effective_messaging 0.8.0 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
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