effective_messaging 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6d5586319e69df10977387b8dde4f21c28b6c091ba2d013c0aa92356c5d64612
4
- data.tar.gz: 30780a50e3482bf8f66492f9b965f3dba832c2ccfd237753517214536484bc6c
3
+ metadata.gz: 473c0318cc028c7e4eced72d90d9f257af48060615ab9368e267aefeb8912923
4
+ data.tar.gz: b113796bf1d6eb615d732817bfd1b773a7341255774b90fc4bc1a48d4074a3a1
5
5
  SHA512:
6
- metadata.gz: 984b176a01cb8fb1e6935975f20fba184e0c4af16783faef38c6372dc76b85a9735b0b8d87723564026a22add1426c1a78b2c0d14c25f906d4b76470119a612e
7
- data.tar.gz: 159df93437962685676e463ad350f91326de8d87efd440027b1da1e8f1b6061552a8d1bf307cdc8ba9fedc9faaf87574754421a2d3794a63410f7caf5518a27d
6
+ metadata.gz: 130940194d693e5dab80793b47a54ae2614f6e8df8ba80d986e20ac35fdd0071a017365807a076ad5ebf2f2e176582b6aa4eb5981fb1a8b0a491497bd062dc1f
7
+ data.tar.gz: '0356873babc77f9124b8164d179fe7852d19a70eb8e4a8fbf5e9966f017c71b69bd9588b61adcdc1b2784f38df6590f202636af690ccf17b71436e6f889118dc'
@@ -9,7 +9,7 @@ module Admin
9
9
  col :notification
10
10
  col :report, visible: !attributes[:inline]
11
11
  col :resource, search: :string
12
- col :user, search: :string
12
+ col :user, label: 'Source', search: :string
13
13
  col :email
14
14
  col :skipped
15
15
 
@@ -1,3 +1,2 @@
1
1
  module EffectiveMessagingHelper
2
-
3
2
  end
@@ -1,63 +1,36 @@
1
1
  module Effective
2
2
  class NotificationsMailer < EffectiveMessaging.parent_mailer_class
3
3
  include EffectiveMailer
4
+ include EffectiveEmailTemplatesMailer
4
5
 
5
- # This is not an EffectiveEmailTemplatesMailer
6
-
7
- def notify(notification, opts = {})
6
+ def notification(notification, resource = nil, opts = {})
8
7
  raise('expected an Effective::Notification') unless notification.kind_of?(Effective::Notification)
9
8
 
10
- # Returns a Hash of params to pass to mail()
11
- # Includes a :to, :from, :subject and :body, etc
12
- rendered = notification.assign_renderer(view_context).render_email
9
+ @assigns = assigns_for(notification, resource)
10
+
11
+ # Find the TO email address for this resource
12
+ to = notification.to_email(resource)
13
+ raise('expected a to email address') unless to.present?
13
14
 
14
15
  # Attach report
15
16
  attach_report!(notification)
16
- rendered.delete(:content_type) if notification.attach_report?
17
-
18
- # Works with effective_logging to associate this email with the notification
19
- headers = headers_for(notification, opts)
17
+ opts.delete(:content_type) if notification.attach_report?
20
18
 
21
19
  # Use postmark broadcast-stream
22
20
  if defined?(Postmark)
23
- headers.merge!(message_stream: 'broadcast-stream')
24
- attach_unsubscribe_link!(rendered)
21
+ opts.merge!(message_stream: 'broadcast-stream')
22
+ append_unsubscribe_link!(notification, opts)
25
23
  end
26
24
 
27
- # Calls effective_resources subject proc, so we can prepend [LETTERS]
28
- subject = subject_for(__method__, rendered.fetch(:subject), notification, opts)
29
-
30
- # Pass everything to mail
31
- mail(rendered.merge(headers).merge(subject: subject))
25
+ mail(to: to, **headers_for(resource, opts))
32
26
  end
33
27
 
34
- # Does not use effective_email_templates mailer
35
- def notify_resource(notification, resource, opts = {})
36
- raise('expected an Effective::Notification') unless notification.kind_of?(Effective::Notification)
37
- raise('expected an acts_as_reportable resource') unless resource.class.try(:acts_as_reportable?)
38
-
39
- # Returns a Hash of params to pass to mail()
40
- # Includes a :to, :from, :subject and :body
41
- rendered = notification.assign_renderer(view_context).render_email(resource)
42
-
43
- # Works with effective_logging to associate this email with the notification
44
- headers = headers_for(notification, opts)
45
-
46
- # Use postmark broadcast-stream
47
- if defined?(Postmark)
48
- headers.merge!(message_stream: 'broadcast-stream')
49
- attach_unsubscribe_link!(rendered)
50
- end
51
-
52
- # Calls effective_resources subject proc, so we can prepend [LETTERS]
53
- subject = subject_for(__method__, rendered.fetch(:subject), resource, opts)
28
+ private
54
29
 
55
- # Pass everything to mail
56
- mail(rendered.merge(headers).merge(subject: subject))
30
+ def assigns_for(notification, resource)
31
+ notification.assign_renderer(view_context).assigns_for(resource)
57
32
  end
58
33
 
59
- private
60
-
61
34
  def attach_report!(notification)
62
35
  return unless notification.attach_report?
63
36
  raise("expected a scheduled email notification") unless notification.scheduled_email?
@@ -74,9 +47,9 @@ module Effective
74
47
  }
75
48
  end
76
49
 
77
- def attach_unsubscribe_link!(rendered)
78
- raise('expected a Hash') unless rendered.kind_of?(Hash)
79
- raise('expected a Hash with a :body') unless rendered.key?(:body)
50
+ def append_unsubscribe_link!(notification, opts)
51
+ raise('expected a Hash') unless opts.kind_of?(Hash)
52
+ raise('expected a Hash with a :body') unless opts.key?(:body)
80
53
 
81
54
  name = EffectiveResources.et('acronym')
82
55
  url = view_context.root_url
@@ -87,10 +60,13 @@ module Effective
87
60
  "Please understand that unsubscribing means you will no longer receive mandatory messages and announcements."
88
61
  ].join(" ")
89
62
 
90
- # Attach unsubscribe link
91
- rendered[:body] = "#{rendered[:body]}\r\n\r\n#{unsubscribe}"
63
+ if notification.email_notification_html?
64
+ opts.merge!(body: "#{opts[:body]}\r\n<br/><p>#{unsubscribe}</p>")
65
+ else
66
+ opts.merge!(body: "#{opts[:body]}\r\n\r\n#{unsubscribe}")
67
+ end
92
68
 
93
- rendered
69
+ true
94
70
  end
95
71
 
96
72
  def mailer_settings
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # This could be called ReportNotification. It only sends notifications for effective_reports right now.
4
+
3
5
  module Effective
4
6
  class Notification < ActiveRecord::Base
5
7
  self.table_name = (EffectiveMessaging.notifications_table_name || :notifications).to_s
@@ -8,6 +10,7 @@ module Effective
8
10
  attr_accessor :current_resource
9
11
  attr_accessor :view_context
10
12
 
13
+ acts_as_email_notification # effective_resources
11
14
  log_changes if respond_to?(:log_changes)
12
15
 
13
16
  # Unused. If we want to use notifications in a has_many way
@@ -17,7 +20,7 @@ module Effective
17
20
  belongs_to :user, polymorphic: true, optional: true
18
21
 
19
22
  # Effective namespace
20
- belongs_to :report, class_name: 'Effective::Report', optional: true
23
+ belongs_to :report, class_name: 'Effective::Report'
21
24
 
22
25
  # Tracks the send outs
23
26
  has_many :notification_logs, dependent: :delete_all
@@ -65,6 +68,7 @@ module Effective
65
68
  from :string
66
69
  cc :string
67
70
  bcc :string
71
+ content_type :string
68
72
 
69
73
  # Background tracking
70
74
  last_notified_at :datetime
@@ -108,7 +112,7 @@ module Effective
108
112
  end
109
113
 
110
114
  validate(if: -> { immediate? && immediate_days.present? && immediate_times.present? }) do
111
- self.errors.add(:immediate_times, "must be 1 when when using every 0 days") if immediate_days == 0 && immediate_times != 1
115
+ errors.add(:immediate_times, "must be 1 when when using every 0 days") if immediate_days == 0 && immediate_times != 1
112
116
  end
113
117
 
114
118
  # Scheduled
@@ -125,28 +129,8 @@ module Effective
125
129
  end
126
130
  end
127
131
 
128
- # Email
129
- validates :from, presence: true, email: true
130
- validates :subject, presence: true, liquid: true
131
- validates :body, presence: true, liquid: true
132
-
133
- # Report
134
- validates :report, presence: true
135
-
136
132
  validate(if: -> { report.present? }) do
137
- errors.add(:report, 'must include an email or user column') unless report.email_report_column || report.user_report_column
138
- end
139
-
140
- validate(if: -> { report.present? && subject.present? }) do
141
- if(invalid = template_variables(body: false) - report_variables).present?
142
- errors.add(:subject, "Invalid variable: #{invalid.to_sentence}")
143
- end
144
- end
145
-
146
- validate(if: -> { report.present? && body.present? }) do
147
- if(invalid = template_variables(subject: false) - report_variables).present?
148
- errors.add(:body, "Invalid variable: #{invalid.to_sentence}")
149
- end
133
+ errors.add(:report, 'must include an email, user, organization or owner column') unless report.email_report_column || report.emailable_report_column
150
134
  end
151
135
 
152
136
  def to_s
@@ -195,15 +179,11 @@ module Effective
195
179
  Array(self[:scheduled_dates]) - [nil, '']
196
180
  end
197
181
 
198
- def template_subject
199
- Liquid::Template.parse(subject)
200
- end
201
-
202
- def template_body
203
- Liquid::Template.parse(body)
182
+ def email_template
183
+ :notification # We always use this email template
204
184
  end
205
185
 
206
- def report_variables
186
+ def email_template_variables
207
187
  assigns_for().keys
208
188
  end
209
189
 
@@ -272,6 +252,20 @@ module Effective
272
252
  scheduled_email? ? notify_by_schedule!(force: force) : notify_by_resources!(force: force)
273
253
  end
274
254
 
255
+ # Returns a message. Do not call deliver.
256
+ def preview
257
+ return unless report.present?
258
+
259
+ if audience_emails?
260
+ # notify_by_schedule
261
+ Effective::NotificationsMailer.notification(self, nil, email_notification_params)
262
+ else
263
+ # notify_by_resources
264
+ resource = report.collection.order('RANDOM()').first
265
+ Effective::NotificationsMailer.notification(self, resource, email_notification_params) if resource
266
+ end
267
+ end
268
+
275
269
  # Operates on every resource in the data source. Sends one email for each row
276
270
  def notify_by_resources!(force: false)
277
271
  notified = 0
@@ -280,7 +274,7 @@ module Effective
280
274
  next unless notifiable?(resource) || force
281
275
 
282
276
  # Send Now functionality. Don't duplicate if it's same day.
283
- next if force && already_notified_today?(resource)
277
+ next if already_notified_today?(resource) && !force
284
278
 
285
279
  print('.')
286
280
 
@@ -289,7 +283,7 @@ module Effective
289
283
  assign_attributes(current_resource: resource)
290
284
 
291
285
  # Send the resource email
292
- Effective::NotificationsMailer.notify_resource(self, resource).deliver_now
286
+ Effective::NotificationsMailer.notification(self, resource, email_notification_params).deliver_now
293
287
 
294
288
  # Log that it was sent
295
289
  build_notification_log(resource: resource).save!
@@ -299,6 +293,7 @@ module Effective
299
293
  rescue => e
300
294
  EffectiveLogger.error(e.message, associated: self) if defined?(EffectiveLogger)
301
295
  ExceptionNotifier.notify_exception(e, data: { notification_id: id, resource_id: resource.id, resource_type: resource.class.name }) if defined?(ExceptionNotifier)
296
+ raise(e) if Rails.env.test? || Rails.env.development?
302
297
  end
303
298
 
304
299
  GC.start if (notified % 250) == 0
@@ -312,7 +307,7 @@ module Effective
312
307
 
313
308
  if notifiable_scheduled? || force
314
309
  begin
315
- Effective::NotificationsMailer.notify(self).deliver_now
310
+ Effective::NotificationsMailer.notification(self, nil, email_notification_params).deliver_now
316
311
 
317
312
  # Log that it was sent
318
313
  build_notification_log(resource: nil).save!
@@ -322,6 +317,7 @@ module Effective
322
317
  rescue => e
323
318
  EffectiveLogger.error(e.message, associated: self) if defined?(EffectiveLogger)
324
319
  ExceptionNotifier.notify_exception(e, data: { notification_id: id }) if defined?(ExceptionNotifier)
320
+ raise(e) if Rails.env.test? || Rails.env.development?
325
321
  end
326
322
 
327
323
  end
@@ -347,7 +343,7 @@ module Effective
347
343
  end
348
344
 
349
345
  def already_notified_today?(resource)
350
- email = resource_email(resource) || resource_user(resource).try(:email)
346
+ email = resource_emails_to_s(resource)
351
347
  raise("expected an email for #{report} #{report&.id} and #{resource} #{resource&.id}") unless email.present?
352
348
 
353
349
  logs = notification_logs.select { |log| log.email == email }
@@ -362,7 +358,7 @@ module Effective
362
358
  def notifiable_immediate?(resource:, date: nil)
363
359
  raise('expected an immediate? notification') unless immediate?
364
360
 
365
- email = resource_email(resource) || resource_user(resource).try(:email)
361
+ email = resource_emails_to_s(resource)
366
362
  raise("expected an email for #{report} #{report&.id} and #{resource} #{resource&.id}") unless email.present?
367
363
 
368
364
  logs = notification_logs.select { |log| log.email == email }
@@ -391,31 +387,11 @@ module Effective
391
387
  end
392
388
  end
393
389
 
394
- def render_email(resource = nil)
395
- raise('expected an acts_as_reportable resource') if resource.present? && !resource.class.try(:acts_as_reportable?)
396
-
397
- to = if audience == 'emails'
398
- audience_emails.presence
399
- elsif audience == 'report'
400
- resource_email(resource) || resource_user(resource).try(:email)
401
- end
402
-
403
- raise('expected a to email address') unless to.present?
404
-
405
- assigns = assigns_for(resource)
406
-
407
- {
408
- to: to,
409
- from: from,
410
- cc: cc.presence,
411
- bcc: bcc.presence,
412
- content_type: CONTENT_TYPES.first,
413
- subject: template_subject.render(assigns),
414
- body: template_body.render(assigns)
415
- }.compact
390
+ def to_email(resource)
391
+ audience == 'emails' ? audience_emails.presence : resource_emails_to_s(resource)
416
392
  end
417
393
 
418
- # We pull the Assigns from 3 places:
394
+ # We pull the Assigns from 2 places:
419
395
  # 1. The report.report_columns
420
396
  # 2. The class's def reportable_view_assigns(view) method
421
397
  def assigns_for(resource = nil)
@@ -432,42 +408,47 @@ module Effective
432
408
  reportable_view_assigns = resource.reportable_view_assigns(renderer).deep_stringify_keys
433
409
  raise('expected notification assigns to return a Hash') unless reportable_view_assigns.kind_of?(Hash)
434
410
 
435
- # Merge all 3
411
+ # Merge all assigns
436
412
  report_assigns.merge(reportable_view_assigns)
437
413
  end
438
414
 
439
415
  def build_notification_log(resource: nil, skipped: false)
440
- user = resource_user(resource)
416
+ emailable = resource_emailable(resource)
441
417
 
442
- email = resource_email(resource) || user.try(:email)
418
+ email = resource_emails_to_s(resource)
443
419
  email ||= audience_emails_to_s if scheduled_email?
444
420
 
445
- notification_logs.build(email: email, report: report, resource: resource, user: user, skipped: skipped)
421
+ notification_logs.build(email: email, report: report, resource: resource, user: emailable, skipped: skipped)
446
422
  end
447
423
 
448
424
  private
449
425
 
450
- def template_variables(body: true, subject: true)
451
- [(template_body.presence if body), (template_subject.presence if subject)].compact.map do |template|
452
- Liquid::ParseTreeVisitor.for(template.root).add_callback_for(Liquid::VariableLookup) do |node|
453
- [node.name, *node.lookups].join('.')
454
- end.visit
455
- end.flatten.uniq.compact
426
+ def audience_emails_to_s
427
+ audience_emails.presence&.join(', ')
456
428
  end
457
429
 
458
- def audience_emails_to_s
459
- audience_emails.presence&.join(',')
430
+ # All emails for this emailable resource
431
+ def resource_emails_to_s(resource)
432
+ emails = Array(resource_email(resource)).map(&:presence).compact
433
+
434
+ if emails.blank? && (emailable = resource_emailable(resource)).present?
435
+ emails = Array(emailable.try(:reportable_emails) || emailable.try(:email)).map(&:presence).compact
436
+ end
437
+
438
+ emails.presence&.join(', ')
460
439
  end
461
440
 
462
- def resource_user(resource)
441
+ # A user, owner, or organization column
442
+ def resource_emailable(resource)
463
443
  return unless resource.present?
464
444
 
465
- column = report&.user_report_column
445
+ column = report&.emailable_report_column
466
446
  return unless column.present?
467
447
 
468
448
  resource.public_send(column.name) || (resource if resource.respond_to?(:email))
469
449
  end
470
450
 
451
+ # An email column
471
452
  def resource_email(resource)
472
453
  return unless resource.present?
473
454
 
@@ -20,7 +20,7 @@ module Effective
20
20
  scope :sorted, -> { order(:id) }
21
21
  scope :deep, -> { includes(:notification, :report, :resource, :user) }
22
22
 
23
- validates :email, presence: true, email: true
23
+ validates :email, presence: true
24
24
 
25
25
  def to_s
26
26
  model_name.human
@@ -1,7 +1,11 @@
1
1
  = effective_form_with(model: [:admin, notification], engine: true) do |f|
2
2
  %h2 Audience
3
3
  %p Please select who the notifications should be sent to
4
- = f.radios :audience, Effective::Notification::AUDIENCES, label: false, buttons: true
4
+
5
+ = f.radios :audience, Effective::Notification::AUDIENCES, label: false, buttons: true,
6
+ 'data-load-ajax-url': effective_messaging.new_admin_notification_path,
7
+ 'data-load-ajax-div': '#effective-messaging-ajax',
8
+ 'data-load-ajax-all': true
5
9
 
6
10
  = f.show_if(:audience, 'report') do
7
11
  %p The notification will be sent to the user or email from the report.
@@ -46,29 +50,30 @@
46
50
  = f.select :report_id, Effective::Report.sorted.notifiable.all,
47
51
  hint: "Please select a #{link_to 'report', effective_reports.admin_reports_path, target: '_blank'} with a user or email column to use as the data source",
48
52
  'data-load-ajax-url': effective_messaging.new_admin_notification_path,
49
- 'data-load-ajax-div': '#effective-messaging-ajax'
53
+ 'data-load-ajax-div': '#effective-messaging-ajax',
54
+ 'data-load-ajax-all': true
50
55
 
51
56
  = f.show_if(:audience, 'emails') do
52
57
  = f.show_if(:schedule_type, 'scheduled') do
53
58
  = f.check_box :attach_report, label: 'Yes, attach a .csv file with the report data', hint: 'only available to scheduled emails sent to specific addresses'
54
59
 
55
- %h2 Notification
56
- %p The following email notification will be sent
57
-
58
- - f.object.from ||= EffectiveMessaging.mailer_froms.first
59
- = f.select :from, mailer_froms_collection()
60
+ = f.show_if(:audience, 'report') do
61
+ = f.hidden_field :attach_report, value: false
60
62
 
61
- = f.email_field :bcc
62
- = f.text_field :subject
63
- = f.text_area :body, rows: 20
63
+ %h2 Notification
64
+ = email_notification_fields(f, :notification)
64
65
 
65
66
  #effective-messaging-ajax
66
67
  - if f.object.report.present?
67
- %p You can use the following variables in the subject and body:
68
-
69
- %ul
70
- - f.object.report_variables.each do |name|
71
- %li {{ #{name} }}
68
+ = card do
69
+ - if f.object.audience == 'report'
70
+ %p You can use the following variables in the subject and body:
71
+
72
+ %ul
73
+ - f.object.email_template_variables.each do |name|
74
+ %li {{ #{name} }}
75
+ - elsif f.object.audience == 'emails'
76
+ %p No variables available for Send to specific address audience.
72
77
 
73
78
  = f.submit do
74
79
  = f.save 'Save'
@@ -14,36 +14,48 @@
14
14
  %h4= notification.report.to_s
15
15
  = render 'admin/reports/report', report: notification.report
16
16
 
17
- - if notification.rows_count > 0
18
- %p Using a random row from the data source, a preview of the notification follows:
17
+ - message = notification.preview()
18
+
19
+ - if message.present?
20
+ - if notification.audience_emails?
21
+ %p A preview of the "Send to specific addresses" notification follows:
22
+ - else
23
+ %p Using a random row from the data source, a preview of the "Send to user or email from the report" notification follows:
19
24
 
20
25
  = card('Preview') do
21
- - resource = notification.report.collection.order('RANDOM()').first
22
- - rendered = notification.assign_renderer(self).render_email(resource)
26
+ - message = notification.preview()
23
27
 
24
28
  %table.table
25
29
  %tbody
26
30
  %tr
27
31
  %th To
28
- %td= rendered.fetch(:to)
32
+ %td= Array(message.to).join(', ')
29
33
  %tr
30
34
  %th From
31
- %td= rendered.fetch(:from)
35
+ %td= Array(message.from).join(', ')
36
+
37
+ - if (content_type = message.content_type).present?
38
+ %tr
39
+ %th Content-Type
40
+ %td= content_type
32
41
 
33
- - if (cc = rendered[:cc]).present?
42
+ - if (cc = message.cc).present?
34
43
  %tr
35
44
  %th CC
36
45
  %td= cc
37
46
 
38
- - if (bcc = rendered[:bcc]).present?
47
+ - if (bcc = message.bcc).present?
39
48
  %tr
40
49
  %th BCC
41
50
  %td= bcc
42
51
 
43
52
  %tr
44
53
  %th Subject
45
- %td= rendered.fetch(:subject)
54
+ %td= message.subject
46
55
 
47
56
  %tr
48
57
  %td{colspan: 2}
49
- = simple_format(rendered.fetch(:body).to_s)
58
+ - if email_message_html?(message)
59
+ = iframe_srcdoc_tag(email_message_body(message))
60
+ - else
61
+ = simple_format(email_message_body(message))
@@ -0,0 +1,10 @@
1
+ ---
2
+ subject: 'Subject of this notification'
3
+ ---
4
+ Hello,
5
+
6
+ You are receiving this notification for the following reason:
7
+
8
+ TODO
9
+
10
+ Thank you
@@ -69,6 +69,7 @@ class CreateEffectiveMessaging < ActiveRecord::Migration[6.0]
69
69
  t.string :from
70
70
  t.string :cc
71
71
  t.string :bcc
72
+ t.string :content_type
72
73
 
73
74
  t.datetime :last_notified_at
74
75
  t.integer :last_notified_count
@@ -1,3 +1,3 @@
1
1
  module EffectiveMessaging
2
- VERSION = '0.8.0'.freeze
2
+ VERSION = '0.9.0'.freeze
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: effective_messaging
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.0
4
+ version: 0.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Code and Effect
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-05-16 00:00:00.000000000 Z
11
+ date: 2024-06-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -246,6 +246,7 @@ files:
246
246
  - app/views/effective/chats/_summary.html.haml
247
247
  - app/views/effective/messaging/_dashboard.html.haml
248
248
  - app/views/effective/messaging_mailer/chat_new_message.liquid
249
+ - app/views/effective/notifications_mailer/notification.liquid
249
250
  - config/effective_messaging.rb
250
251
  - config/locales/effective_messaging.yml
251
252
  - config/routes.rb