effective_messaging 0.1.8 → 0.2.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: e35295ff8cf6a9a6853b90d759fac39fab3bc3c72525811069334ab8cf3fef34
4
- data.tar.gz: aa9ae425f27a31c1451022c85c4d725fc5021c6086e3df9c2692294182288962
3
+ metadata.gz: ca64445b74c99e0eac989eb22c606a4fa588d05cbfefdd7401432266ab201b06
4
+ data.tar.gz: 0b25f92b272fd9f9903d7757a383983224de064ff9aae4dd6c56fa6b48187246
5
5
  SHA512:
6
- metadata.gz: ee5b67860a4d555136248666481efd153549b9a1a1503a577e35c30d3f1f3d05933585dfed3a3be1f0fb6793211ef597c2bc187957fff8a0777fdd01b3ee36b0
7
- data.tar.gz: 6bd025233cd26a5f6e99cd22e6601e190423dc3e3990d9ccb9976161a2436aaf56dfba8fa56f405c16fcbb8c2776b8d127d9fc81c12d4b11588ebe0752e91bfd
6
+ metadata.gz: 8f5522b4cf8939a196f339d95dbb2f5bb61ccd00a4dc07574efa64541ad13120366586c6c772494039ecd66af20d30e1bc61801508b5000f2f68d6eb7d395461
7
+ data.tar.gz: c40bcf7fa920670af552585bd7eb0c2ce0de440b5bfd66f990a178d630fb0de5e8c9be2559584b9de5da39550fb5a29e05798537cb093148e076d18d2c01b26b
@@ -0,0 +1,15 @@
1
+ module Admin
2
+ class NotificationLogsController < ApplicationController
3
+ before_action(:authenticate_user!) if defined?(Devise)
4
+ before_action { EffectiveResources.authorize!(self, :admin, :effective_messaging) }
5
+
6
+ include Effective::CrudController
7
+
8
+ private
9
+
10
+ def permitted_params
11
+ params.require(:effective_notification_log).permit!
12
+ end
13
+
14
+ end
15
+ end
@@ -5,7 +5,7 @@ module Admin
5
5
 
6
6
  include Effective::CrudController
7
7
 
8
- submit :create_notification_job, 'Send Now'
8
+ submit :send_now, 'Send Now'
9
9
 
10
10
  private
11
11
 
@@ -0,0 +1,22 @@
1
+ module Admin
2
+ class EffectiveNotificationLogsDatatable < Effective::Datatable
3
+ datatable do
4
+ order :created_at
5
+
6
+ col :id, visible: false
7
+ col :created_at, as: :date, label: 'Date'
8
+
9
+ col :notification
10
+ col :report, visible: !attributes[:inline]
11
+ col :resource
12
+ col :user
13
+ col :email, visible: false
14
+
15
+ actions_col
16
+ end
17
+
18
+ collection do
19
+ Effective::NotificationLog.deep.all
20
+ end
21
+ end
22
+ end
@@ -2,8 +2,8 @@ module Admin
2
2
  class EffectiveNotificationsDatatable < Effective::Datatable
3
3
  filters do
4
4
  scope :all
5
- scope :notifiable, label: 'Upcoming'
6
- scope :completed, label: 'Past'
5
+ scope :enabled
6
+ scope :disabled
7
7
  end
8
8
 
9
9
  datatable do
@@ -13,8 +13,26 @@ module Admin
13
13
  col :created_at, visible: false
14
14
  col :id, visible: false
15
15
 
16
- col :send_at
17
- col :report, search: Effective::Report.emails.sorted
16
+ col :audience, visible: false
17
+
18
+ col :audience_emails, label: 'Send to' do |notification|
19
+ if notification.audience == 'emails'
20
+ notification.audience_emails.join(', ')
21
+ else
22
+ 'report user'
23
+ end
24
+ end
25
+
26
+ col :enabled
27
+
28
+ col :schedule
29
+ col :schedule_type, visible: false
30
+ col :immediate_days, visible: false
31
+ col :immediate_times, visible: false
32
+ col :scheduled_method, visible: false
33
+ col :scheduled_dates, visible: false
34
+
35
+ col :report, search: Effective::Report.notifiable.sorted
18
36
 
19
37
  col :subject
20
38
  col :body, visible: false
@@ -23,9 +41,8 @@ module Admin
23
41
  col :cc, visible: false
24
42
  col :bcc, visible: false
25
43
 
26
- col :started_at, visible: false
27
- col :completed_at
28
- col :notifications_sent, visible: false
44
+ col :last_notified_at
45
+ col :last_notified_count
29
46
 
30
47
  actions_col
31
48
  end
@@ -1,9 +1,8 @@
1
1
  module Effective
2
2
  class NotificationJob < ApplicationJob
3
3
 
4
- def perform(id, force)
5
- notification = Notification.find(id)
6
- notification.notify!(force: force)
4
+ def perform(id)
5
+ Notification.find(id).notify!
7
6
  end
8
7
 
9
8
  end
@@ -2,11 +2,33 @@ module Effective
2
2
  class NotificationsMailer < EffectiveMessaging.parent_mailer_class
3
3
  include EffectiveMailer
4
4
 
5
- # Does not use effective_email_templates mailer
5
+ def notify(notification, opts = {})
6
+ raise('expected an Effective::Notification') unless notification.kind_of?(Effective::Notification)
7
+
8
+ # Returns a Hash of params to pass to mail()
9
+ # Includes a :to, :from, etc
10
+ rendered = notification.render_email
11
+
12
+ # Attach report
13
+ attach_report!(notification)
14
+
15
+ # Works with effective_logging to associate this email with the notification
16
+ headers = headers_for(notification, opts)
17
+
18
+ # Use postmark broadcast-stream
19
+ headers.merge!(message_stream: 'broadcast-stream') if defined?(Postmark)
20
+
21
+ # Calls effective_resources subject proc, so we can prepend [LETTERS]
22
+ subject = subject_for(__method__, rendered.fetch(:subject), notification, opts)
6
23
 
7
- def notification(notification, resource, opts = {})
24
+ # Pass everything to mail
25
+ mail(rendered.merge(headers).merge(subject: subject))
26
+ end
27
+
28
+ # Does not use effective_email_templates mailer
29
+ def notify_resource(notification, resource, opts = {})
8
30
  raise('expected an Effective::Notification') unless notification.kind_of?(Effective::Notification)
9
- raise('expected a resource') unless resource.present?
31
+ raise('expected an acts_as_reportable resource') unless resource.class.try(:acts_as_reportable?)
10
32
 
11
33
  # Returns a Hash of params to pass to mail()
12
34
  # Includes a :to, :from, etc
@@ -16,9 +38,7 @@ module Effective
16
38
  headers = headers_for(notification, opts)
17
39
 
18
40
  # Use postmark broadcast-stream
19
- if defined?(Postmark)
20
- headers.merge!(message_stream: 'broadcast-stream')
21
- end
41
+ headers.merge!(message_stream: 'broadcast-stream') if defined?(Postmark)
22
42
 
23
43
  # Calls effective_resources subject proc, so we can prepend [LETTERS]
24
44
  subject = subject_for(__method__, rendered.fetch(:subject), resource, opts)
@@ -29,9 +49,30 @@ module Effective
29
49
 
30
50
  private
31
51
 
52
+ def attach_report!(notification)
53
+ return unless notification.attach_report?
54
+ raise("expected a scheduled email notification") unless notification.scheduled_email?
55
+
56
+ report = notification.report
57
+ raise("expected a report for notification id=#{notification.id}") unless report.present?
58
+
59
+ # Attach Report CSV built from datatables
60
+ datatable = EffectiveReportDatatable.new(view_context, report: report)
61
+
62
+ attachments["#{Time.zone.now.strftime('%F')}-#{report.to_s.parameterize}.csv"] = {
63
+ mime_type: datatable.csv_content_type,
64
+ content: datatable.csv_file
65
+ }
66
+ end
67
+
32
68
  def mailer_settings
33
69
  EffectiveMessaging
34
70
  end
35
71
 
72
+ # Authorization for the Datatables.
73
+ def authorize!(action, resource)
74
+ true
75
+ end
76
+
36
77
  end
37
78
  end
@@ -16,13 +16,48 @@ module Effective
16
16
  belongs_to :user, polymorphic: true, optional: true
17
17
 
18
18
  # Effective namespace
19
- belongs_to :report, class_name: 'Effective::Report'
19
+ belongs_to :report, class_name: 'Effective::Report', optional: true
20
+
21
+ # Tracks the send outs
22
+ has_many :notification_logs, dependent: :delete_all
23
+ accepts_nested_attributes_for :notification_logs
24
+
25
+ AUDIENCES = [
26
+ ['Send to user or email from the report', 'report'],
27
+ ['Send to specific addresses', 'emails']
28
+ ]
29
+
30
+ SCHEDULE_TYPES = [
31
+ ['On the first day they appear in the report and every x days thereafter', 'immediate'],
32
+ ['When present in the report on the following dates', 'scheduled']
33
+ ]
34
+
35
+ # TODO: ['Send once', 'Send daily', 'Send weekly', 'Send monthly', 'Send quarterly', 'Send yearly', 'Send now']
36
+ SCHEDULED_METHODS = [
37
+ ['The following dates...', 'dates'],
38
+ ]
20
39
 
21
40
  CONTENT_TYPES = ['text/plain', 'text/html']
22
41
 
23
42
  effective_resource do
24
- send_at :datetime
43
+ audience :string
44
+ audience_emails :text
45
+
46
+ enabled :boolean
47
+ attach_report :boolean
25
48
 
49
+ schedule_type :string
50
+
51
+ # When the schedule is immediate. We send the email when they first appear in the data source
52
+ # And then every immediate_days after for immediate_times
53
+ immediate_days :integer
54
+ immediate_times :integer
55
+
56
+ # When the schedule id scheduled. We send the email to everyone in the audience on the given dates
57
+ scheduled_method :string
58
+ scheduled_dates :text
59
+
60
+ # Email
26
61
  subject :string
27
62
  body :text
28
63
 
@@ -30,51 +65,122 @@ module Effective
30
65
  cc :string
31
66
  bcc :string
32
67
 
33
- # Tracking background jobs email send out
34
- started_at :datetime
35
- completed_at :datetime
36
- notifications_sent :integer
68
+ # Background tracking
69
+ last_notified_at :datetime
70
+ last_notified_count :integer
37
71
 
38
72
  timestamps
39
73
  end
40
74
 
75
+ serialize :audience_emails, Array
76
+ serialize :scheduled_dates, Array
77
+
41
78
  scope :sorted, -> { order(:id) }
42
79
  scope :deep, -> { includes(report: :report_columns) }
43
80
 
44
- scope :upcoming, -> { where('send_at > ?', Time.zone.now) }
45
- scope :started, -> { where.not(started_at: nil) }
46
- scope :completed, -> { where.not(completed_at: nil) }
81
+ scope :enabled, -> { where(enabled: true) }
82
+ scope :disabled, -> { where(enabled: false) }
83
+
84
+ before_validation do
85
+ self.from ||= EffectiveMessaging.froms.first
86
+ end
87
+
88
+ # Emails or Report
89
+ validates :audience, presence: true, inclusion: { in: AUDIENCES.map(&:last) }
90
+ validates :audience_emails, presence: true, if: -> { audience_emails? }
91
+
92
+ # Scheduled or Immediate
93
+ validates :schedule_type, presence: true, inclusion: { in: SCHEDULE_TYPES.map(&:last) }
94
+
95
+ # Attach Report - Only for scheduled emails
96
+ validates :attach_report, absence: true, unless: -> { scheduled_email? }
97
+
98
+ # Immediate
99
+ with_options(if: -> { immediate? }) do
100
+ validates :immediate_days, presence: true, numericality: { greater_than_or_equal_to: 0 }
101
+ validates :immediate_times, presence: true, numericality: { greater_than_or_equal_to: 1 }
102
+ end
47
103
 
48
- # Called by the notifier rake task
49
- scope :notifiable, -> { where(started_at: nil) }
104
+ # Scheduled
105
+ validates :scheduled_method, presence: true, inclusion: { in: SCHEDULED_METHODS.map(&:last) }, if: -> { scheduled? }
50
106
 
51
- before_validation(if: -> { send_at_changed? }) do
52
- assign_attributes(started_at: nil, completed_at: nil, notifications_sent: nil)
107
+ with_options(if: -> { scheduled_method.to_s == 'dates' }) do
108
+ validates :scheduled_dates, presence: true
53
109
  end
54
110
 
55
- validates :send_at, presence: true
111
+ validate(if: -> { scheduled_dates.present? }) do
112
+ scheduled_dates.each do |str|
113
+ errors.add(:scheduled_dates, "expected a string") unless str.kind_of?(String)
114
+ errors.add(:scheduled_dates, "#{str} is an invalid date") unless (Time.zone.parse(str) rescue false)
115
+ end
116
+ end
117
+
118
+ # Email
56
119
  validates :from, presence: true, email: true
57
120
  validates :subject, presence: true, liquid: true
58
121
  validates :body, presence: true, liquid: true
59
122
 
123
+ # Report
124
+ validates :report, presence: true
125
+
60
126
  validate(if: -> { report.present? }) do
61
- self.errors.add(:report, 'must include an email column') unless report.email_report_column.present?
127
+ errors.add(:report, 'must include an email or user column') unless report.email_report_column || report.user_report_column
62
128
  end
63
129
 
64
130
  validate(if: -> { report.present? && subject.present? }) do
65
131
  if(invalid = template_variables(body: false) - report_variables).present?
66
- self.errors.add(:subject, "Invalid variable: #{invalid.to_sentence}")
132
+ errors.add(:subject, "Invalid variable: #{invalid.to_sentence}")
67
133
  end
68
134
  end
69
135
 
70
136
  validate(if: -> { report.present? && body.present? }) do
71
137
  if(invalid = template_variables(subject: false) - report_variables).present?
72
- self.errors.add(:body, "Invalid variable: #{invalid.to_sentence}")
138
+ errors.add(:body, "Invalid variable: #{invalid.to_sentence}")
73
139
  end
74
140
  end
75
141
 
76
142
  def to_s
77
- subject.presence || 'notification'
143
+ subject.presence || model_name.human
144
+ end
145
+
146
+ def schedule
147
+ if immediate?
148
+ "Send immediately then every #{immediate_days} days for #{immediate_times} times total"
149
+ elsif scheduled? && scheduled_method == 'dates'
150
+ "Send on #{scheduled_dates.length} scheduled days: #{scheduled_dates.sort.to_sentence}"
151
+ else
152
+ 'todo'
153
+ end
154
+ end
155
+
156
+ def immediate?
157
+ schedule_type == 'immediate'
158
+ end
159
+
160
+ def scheduled?
161
+ schedule_type == 'scheduled'
162
+ end
163
+
164
+ def audience_emails?
165
+ audience == 'emails'
166
+ end
167
+
168
+ def audience_report?
169
+ audience == 'report'
170
+ end
171
+
172
+ # Only scheduled emails can have attached reports.
173
+ # Only scheduled emails can do Send Now
174
+ def scheduled_email?
175
+ scheduled? && audience_emails?
176
+ end
177
+
178
+ def audience_emails
179
+ Array(self[:audience_emails]) - [nil, '']
180
+ end
181
+
182
+ def scheduled_dates
183
+ Array(self[:scheduled_dates]) - [nil, '']
78
184
  end
79
185
 
80
186
  def template_subject
@@ -93,53 +199,111 @@ module Effective
93
199
  @rows_count ||= report.collection().count if report
94
200
  end
95
201
 
96
- def in_progress?
97
- started_at.present? && completed_at.blank?
98
- end
99
-
100
- def notifiable?
101
- started_at.blank? && completed_at.blank?
202
+ # Button on the Admin interface. Enqueues the job to send right away.
203
+ def send_now!
204
+ raise('expected to be persisted') unless persisted?
205
+ NotificationJob.perform_later(id)
206
+ true
102
207
  end
103
208
 
104
- def notify_now?
105
- notifiable? && Time.zone.now >= send_at
209
+ # The main function
210
+ def notify!
211
+ scheduled_email? ? notify_by_schedule! : notify_by_resources!
106
212
  end
107
213
 
108
- def notify!(force: false, limit: nil)
109
- return false unless (notify_now? || force)
110
-
111
- update!(started_at: Time.zone.now, completed_at: nil, notifications_sent: nil)
112
-
113
- index = 0
214
+ # Operates on every resource in the data source. Sends one email for each row
215
+ def notify_by_resources!
216
+ notified = 0
114
217
 
115
218
  report.collection().find_each do |resource|
219
+ next unless notifiable?(resource)
116
220
  print('.')
117
221
 
222
+ # For logging
118
223
  assign_attributes(current_resource: resource)
119
- Effective::NotificationsMailer.notification(self, resource).deliver_now
120
224
 
121
- index += 1
122
- break if limit && index >= limit
225
+ # Send the resource email
226
+ build_notification_log(resource: resource).save!
227
+ Effective::NotificationsMailer.notify_resource(self, resource).deliver_now
228
+
229
+ notified += 1
123
230
 
124
- GC.start if (index % 250) == 0
231
+ GC.start if (notified % 250) == 0
125
232
  end
126
233
 
127
- update!(current_resource: nil, completed_at: Time.zone.now, notifications_sent: index)
234
+ notified > 0 ? update!(last_notified_at: Time.zone.now, last_notified_count: notified) : touch
128
235
  end
129
236
 
130
- # The 'Send Now' action on admin. Enqueues a job that calls notify!(force: true)
131
- def create_notification_job!
132
- update!(started_at: Time.zone.now, completed_at: nil, notifications_sent: nil)
237
+ def notify_by_schedule!
238
+ notified = 0
133
239
 
134
- NotificationJob.perform_later(id, true) # force = true
135
- true
240
+ if notifiable_scheduled?
241
+ build_notification_log(resource: nil).save!
242
+ Effective::NotificationsMailer.notify(self).deliver_now
243
+ notified += 1
244
+ end
245
+
246
+ notified > 0 ? update!(last_notified_at: Time.zone.now, last_notified_count: notified) : touch
247
+ end
248
+
249
+ def notifiable?(resource)
250
+ raise('expected an acts_as_reportable resource') unless resource.class.try(:acts_as_reportable?)
251
+
252
+ if schedule_type == 'immediate'
253
+ notifiable_immediate?(resource: resource)
254
+ elsif schedule_type == 'scheduled'
255
+ notifiable_scheduled?(date: nil)
256
+ else
257
+ raise("unsupported schedule_type")
258
+ end
136
259
  end
137
260
 
138
- def render_email(resource)
139
- raise('expected a resource') unless resource.present?
261
+ # Consider the notification logs which track how many and how long ago this notification was sent
262
+ # It's notifiable? when first time or if it's been immediate_days since last notification
263
+ def notifiable_immediate?(resource:)
264
+ raise('expected an immexiate? notification') unless immediate?
265
+
266
+ email = resource_email(resource) || resource_user(resource).try(:email)
267
+ raise("expected an email for #{report} #{report&.id} and #{resource} #{resource&.id}") unless email.present?
268
+
269
+ logs = notification_logs.select { |log| log.email == email }
270
+
271
+ if logs.count == 0
272
+ true # This is the first time. We should send.
273
+ elsif logs.count < immediate_times
274
+ # We still have to send it but consider dates.
275
+ last_sent_days_ago = logs.map(&:days_ago).min || 0
276
+ last_sent_days_ago >= immediate_days
277
+ else
278
+ false # We've already sent enough times
279
+ end
280
+ end
281
+
282
+ def notifiable_scheduled?(date: nil)
283
+ raise('expected a scheduled? notification') unless scheduled?
284
+
285
+ date ||= Time.zone.now.beginning_of_day
286
+
287
+ case scheduled_method
288
+ when 'dates'
289
+ scheduled_dates.find { |day| day == date.strftime('%F') }.present?
290
+ else
291
+ raise('unsupported scheduled_method')
292
+ end
293
+ end
294
+
295
+ def render_email(resource = nil)
296
+ raise('expected an acts_as_reportable resource') if resource.present? && !resource.class.try(:acts_as_reportable?)
297
+
298
+ to = if audience == 'emails'
299
+ audience_emails.presence
300
+ elsif audience == 'report'
301
+ resource_email(resource) || resource_user(resource).try(:email)
302
+ end
303
+
304
+ raise('expected a to email address') unless to.present?
140
305
 
141
306
  assigns = assigns_for(resource)
142
- to = assigns.fetch(report.email_report_column.name) || raise('expected an email assigns')
143
307
 
144
308
  {
145
309
  to: to,
@@ -153,12 +317,23 @@ module Effective
153
317
  end
154
318
 
155
319
  def assigns_for(resource)
320
+ return {} unless resource.present?
321
+
156
322
  Array(report&.report_columns).inject({}) do |h, column|
157
323
  value = resource.send(column.name)
158
324
  h[column.name] = column.format(value); h
159
325
  end
160
326
  end
161
327
 
328
+ def build_notification_log(resource: nil)
329
+ user = resource_user(resource)
330
+
331
+ email = resource_email(resource) || user.try(:email)
332
+ email ||= audience_emails_to_s if scheduled_email?
333
+
334
+ notification_logs.build(email: email, report: report, resource: resource, user: user)
335
+ end
336
+
162
337
  private
163
338
 
164
339
  def template_variables(body: true, subject: true)
@@ -169,5 +344,27 @@ module Effective
169
344
  end.flatten.uniq.compact
170
345
  end
171
346
 
347
+ def audience_emails_to_s
348
+ audience_emails.presence&.join(',')
349
+ end
350
+
351
+ def resource_user(resource)
352
+ return unless resource.present?
353
+
354
+ column = report&.user_report_column
355
+ return unless column.present?
356
+
357
+ resource.public_send(column.name) || (resource if resource.respond_to?(:email))
358
+ end
359
+
360
+ def resource_email(resource)
361
+ return unless resource.present?
362
+
363
+ column = report&.email_report_column
364
+ return unless column.present?
365
+
366
+ resource.public_send(column.name)
367
+ end
368
+
172
369
  end
173
370
  end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Effective
4
+ class NotificationLog < ActiveRecord::Base
5
+ self.table_name = EffectiveMessaging.notification_logs_table_name.to_s
6
+
7
+ belongs_to :notification
8
+
9
+ belongs_to :report, class_name: 'Effective::Report', optional: true
10
+ belongs_to :resource, polymorphic: true, optional: true
11
+ belongs_to :user, polymorphic: true, optional: true
12
+
13
+ effective_resource do
14
+ email :string
15
+
16
+ timestamps
17
+ end
18
+
19
+ scope :sorted, -> { order(:id) }
20
+ scope :deep, -> { includes(:notification, :report, :resource, :user) }
21
+
22
+ validates :email, presence: true, email: true
23
+
24
+ def to_s
25
+ model_name.human
26
+ end
27
+
28
+ def days_ago
29
+ now = Time.zone.now.to_date
30
+ (now - (created_at&.to_date || now)).to_i
31
+ end
32
+
33
+ end
34
+ end
@@ -0,0 +1 @@
1
+ = effective_table_with(notification_log)
@@ -3,5 +3,9 @@
3
3
  = render 'admin/notifications/form_notification', notification: notification
4
4
 
5
5
  - if notification.persisted? && notification.respond_to?(:logs_datatable)
6
+ = tab 'Notification Logs' do
7
+ - datatable = Admin::EffectiveNotificationLogsDatatable.new(notification: notification)
8
+ = render_inline_datatable(datatable)
9
+
6
10
  = tab 'Logs' do
7
- = render_datatable(notification.logs_datatable)
11
+ = render_inline_datatable(notification.logs_datatable)
@@ -1,35 +1,55 @@
1
1
  = effective_form_with(model: [:admin, notification], engine: true) do |f|
2
- %h2 Data Source
3
- %p
4
- Please select a
5
- = link_to 'report', effective_reports.admin_reports_path, target: '_blank'
6
- with an email report column to use as the data source.
2
+ %h2 Audience
3
+ %p Please select who the notifications should be sent to
4
+ = f.radios :audience, Effective::Notification::AUDIENCES, label: false, buttons: true
7
5
 
8
- %p Each result from the report will be sent a notification.
6
+ = f.show_if(:audience, 'report') do
7
+ %p The notification will be sent to the user or email from the report.
9
8
 
10
- .row
11
- .col-md-6
12
- = f.select :report_id, Effective::Report.sorted.emails.all, label: 'Report with email column',
13
- 'data-load-ajax-url': effective_messaging.new_admin_notification_path,
14
- 'data-load-ajax-div': '#effective-messaging-ajax'
9
+ = f.show_if(:audience, 'emails') do
10
+ %p The notification will be sent to the following address(es):
11
+
12
+ = f.select :audience_emails, f.object.audience_emails, label: 'Send to', multiple: true, tags: true, hint: 'Add one or more email address by pressing enter'
15
13
 
16
14
  %h2 Schedule
17
- %p Please select a date when the notifications should be sent
15
+ %p Please select how and when the notifications should be sent
16
+
17
+ = f.check_box :enabled, label: "Yes, this notification is enabled and notification emails should be sent"
18
+
19
+ = f.show_if(:enabled, true) do
20
+ = f.select :schedule_type, Effective::Notification::SCHEDULE_TYPES
18
21
 
22
+ = f.show_if(:schedule_type, 'immediate') do
23
+ .d-flex
24
+ Send notification immediately and then every
25
+ .mx-3= f.number_field :immediate_days, label: false, min: 0, max: 365
26
+ day(s) thereafter, for
27
+ .mx-3= f.number_field :immediate_times, label: false, min: 1, max: 1000
28
+ total notification(s).
29
+
30
+ = f.show_if(:schedule_type, 'scheduled') do
31
+ /= f.radios :scheduled_method, Effective::Notification::SCHEDULED_METHODS, label: false, required: true
32
+ = f.hidden_field :scheduled_method, value: 'dates'
33
+
34
+ /= f.show_if(:scheduled_method, 'dates') do
35
+ %p Send notification on the following scheduled dates:
36
+ = f.select :scheduled_dates, f.object.scheduled_dates, label: false, multiple: true, tags: true, hint: 'Add one or more dates by pressing enter. Please input in the format YYYY-MM-DD'
37
+
38
+
39
+ %h2 Report
19
40
  .row
20
41
  .col-md-6
21
- - minDate = [f.object.created_at, Time.zone.now].compact.min
42
+ = f.select :report_id, Effective::Report.sorted.notifiable.all,
43
+ 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",
44
+ 'data-load-ajax-url': effective_messaging.new_admin_notification_path,
45
+ 'data-load-ajax-div': '#effective-messaging-ajax'
22
46
 
23
- = f.datetime_field :send_at, label: 'Send the notification at', input_js: { minDate: minDate.strftime('%F %H:%M:%S') },
24
- hint: 'A future date. Changing this value will reset the started_at and completed_at dates so this notification can be sent again.'
25
- .col-md-6
26
- - if f.object.completed_at.present?
27
- = f.static_field :completed_at
28
- - elsif f.object.started_at.present?
29
- = f.static_field :started_at
47
+ = f.show_if(:audience, 'emails') do
48
+ = f.show_if(:schedule_type, 'scheduled') do
49
+ = 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'
30
50
 
31
51
  %h2 Notification
32
- %p The following notification will be sent to each row in the data source
52
+ %p The following email notification will be sent
33
53
 
34
54
  - froms = Array(EffectiveMessaging.froms)
35
55
 
@@ -39,6 +59,7 @@
39
59
  - else
40
60
  = f.email_field :from
41
61
 
62
+ = f.email_field :bcc
42
63
  = f.text_field :subject
43
64
  = f.text_area :body
44
65
 
@@ -53,8 +74,8 @@
53
74
  = f.submit do
54
75
  = f.save 'Save'
55
76
 
56
- - if f.object.persisted? && !f.object.in_progress?
57
- = f.save 'Send Now', class: 'btn btn-warning', 'data-confirm': "Really send #{pluralize(notification.rows_count, 'notification')} now?"
77
+ - if EffectiveResources.authorized?(self, :send_now, f.object)
78
+ = f.save 'Send Now', class: 'btn btn-warning', 'data-confirm': "Really send now?"
58
79
 
59
80
  = f.save 'Add New', class: 'btn btn-secondary'
60
81
  = f.save 'Continue', class: 'btn btn-secondary'
@@ -6,31 +6,7 @@
6
6
  %p Currently there are #{pluralize(notification.rows_count, 'rows')} that would be notified.
7
7
 
8
8
  = card(notification) do
9
- %table.table
10
- %tbody
11
- %tr
12
- %th Send At
13
- %td= notification.send_at.strftime('%F %H:%M')
14
-
15
- %tr
16
- %th Report
17
- %td= link_to(notification.report, effective_reports.admin_report_path(notification.report), target: '_blank')
18
-
19
- %tr
20
- %th Current rows count
21
- %td= notification.rows_count
22
-
23
- %tr
24
- %th Started At
25
- %td= notification.started_at&.strftime('%F %H:%M') || 'Never'
26
-
27
- %tr
28
- %th Completed At
29
- %td= notification.completed_at&.strftime('%F %H:%M') || 'Never'
30
-
31
- %tr
32
- %th Notifications Sent
33
- %td= notification.notifications_sent.presence || 'None'
9
+ = effective_table_with(notification)
34
10
 
35
11
  - if notification.rows_count > 0
36
12
  %p Using a random row from the data source, a preview of the notification follows:
@@ -3,6 +3,7 @@ EffectiveMessaging.setup do |config|
3
3
  config.chat_users_table_name = :chat_users
4
4
  config.chat_messages_table_name = :chat_messages
5
5
  config.notifications_table_name = :notifications
6
+ config.notification_logs_table_name = :notification_logs
6
7
 
7
8
  # Layout Settings
8
9
  # Configure the Layout per controller, or all at once
data/config/routes.rb CHANGED
@@ -14,6 +14,7 @@ EffectiveMessaging::Engine.routes.draw do
14
14
  resources :chats
15
15
  resources :chat_messages, only: [:index, :show, :destroy]
16
16
  resources :notifications
17
+ resources :notification_logs, only: [:index, :destroy]
17
18
  end
18
19
 
19
20
  end
@@ -49,7 +49,19 @@ class CreateEffectiveMessaging < ActiveRecord::Migration[6.0]
49
49
 
50
50
  t.integer :report_id
51
51
 
52
- t.datetime :send_at
52
+ t.string :audience
53
+ t.text :audience_emails
54
+
55
+ t.boolean :enabled, default: false
56
+ t.boolean :attach_report, default: false
57
+
58
+ t.string :schedule_type
59
+
60
+ t.integer :immediate_days
61
+ t.integer :immediate_times
62
+
63
+ t.string :scheduled_method
64
+ t.text :scheduled_dates
53
65
 
54
66
  t.string :subject
55
67
  t.text :body
@@ -58,9 +70,23 @@ class CreateEffectiveMessaging < ActiveRecord::Migration[6.0]
58
70
  t.string :cc
59
71
  t.string :bcc
60
72
 
61
- t.datetime :started_at
62
- t.datetime :completed_at
63
- t.integer :notifications_sent
73
+ t.datetime :last_notified_at
74
+ t.integer :last_notified_count
75
+
76
+ t.timestamps
77
+ end
78
+
79
+ create_table <%= @notification_logs_table_name %> do |t|
80
+ t.integer :notification_id
81
+ t.integer :report_id
82
+
83
+ t.integer :user_id
84
+ t.string :user_type
85
+
86
+ t.integer :resource_id
87
+ t.string :resource_type
88
+
89
+ t.string :email
64
90
 
65
91
  t.timestamps
66
92
  end
@@ -1,3 +1,3 @@
1
1
  module EffectiveMessaging
2
- VERSION = '0.1.8'.freeze
2
+ VERSION = '0.2.0'.freeze
3
3
  end
@@ -7,7 +7,7 @@ module EffectiveMessaging
7
7
 
8
8
  def self.config_keys
9
9
  [
10
- :chats_table_name, :chat_users_table_name, :chat_messages_table_name, :notifications_table_name,
10
+ :chats_table_name, :chat_users_table_name, :chat_messages_table_name, :notifications_table_name, :notification_logs_table_name,
11
11
  :layout,
12
12
  :froms,
13
13
  :mailer, :parent_mailer, :deliver_method, :mailer_layout, :mailer_sender, :mailer_admin, :mailer_subject
@@ -20,4 +20,8 @@ module EffectiveMessaging
20
20
  mailer&.constantize || Effective::MessagingMailer
21
21
  end
22
22
 
23
+ def self.Notification
24
+ Effective::Notification
25
+ end
26
+
23
27
  end
@@ -24,6 +24,7 @@ module EffectiveMessaging
24
24
  @chat_users_table_name = ':' + EffectiveMessaging.chat_users_table_name.to_s
25
25
  @chat_messages_table_name = ':' + EffectiveMessaging.chat_messages_table_name.to_s
26
26
  @notifications_table_name = ':' + EffectiveMessaging.notifications_table_name.to_s
27
+ @notification_logs_table_name = ':' + EffectiveMessaging.notification_logs_table_name.to_s
27
28
 
28
29
  migration_template ('../' * 3) + 'db/migrate/01_create_effective_messaging.rb.erb', 'db/migrate/create_effective_messaging.rb'
29
30
  end
@@ -1,22 +1,24 @@
1
- # rake effective_messaging:notify
1
+ # rake effective_messaging:send_notifications
2
2
  namespace :effective_messaging do
3
3
  desc 'Sends scheduled notifications for effective messaging'
4
- task notify: :environment do
4
+ task send_notifications: :environment do
5
5
  puts 'Sending notifications'
6
6
 
7
- notifications = Effective::Notification.all.deep.notifiable
7
+ table = ActiveRecord::Base.connection.table_exists?(:notifications)
8
+ blank_tenant = defined?(Tenant) && Tenant.current.blank?
8
9
 
9
- notifications.find_each do |notification|
10
- begin
11
- notified = notification.notify!
12
- Rails.logger.info "Sent notifications for #{notification.report}" if notified
13
- rescue => e
14
- if defined?(ExceptionNotifier)
10
+ if table && !blank_tenant
11
+ notifications = Effective::Notification.all.deep.enabled
12
+
13
+ notifications.find_each do |notification|
14
+ begin
15
+ notification.notify!
16
+ Rails.logger.info "Sent notifications for #{notification} and #{notification.report}"
17
+ rescue => e
15
18
  data = { notification_id: notification.id, report_id: notification.report_id, resource_id: notification.current_resource&.id }
16
- ExceptionNotifier.notify_exception(e, data: data)
19
+ ExceptionNotifier.notify_exception(e, data: data) if defined?(ExceptionNotifier)
20
+ puts "Error with effective_messaging #{notification.id} resource #{notification.current_resource&.id}: #{e.errors.inspect}"
17
21
  end
18
-
19
- puts "Error with effective_messaging #{notification.id} resource #{notification.current_resource&.id}: #{e.errors.inspect}"
20
22
  end
21
23
  end
22
24
 
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.1.8
4
+ version: 0.2.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: 2023-03-10 00:00:00.000000000 Z
11
+ date: 2023-05-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -123,7 +123,7 @@ dependencies:
123
123
  - !ruby/object:Gem::Version
124
124
  version: '0'
125
125
  - !ruby/object:Gem::Dependency
126
- name: haml-rails
126
+ name: haml
127
127
  requirement: !ruby/object:Gem::Requirement
128
128
  requirements:
129
129
  - - ">="
@@ -150,6 +150,20 @@ dependencies:
150
150
  - - ">="
151
151
  - !ruby/object:Gem::Version
152
152
  version: '0'
153
+ - !ruby/object:Gem::Dependency
154
+ name: timecop
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - ">="
158
+ - !ruby/object:Gem::Version
159
+ version: '0'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - ">="
165
+ - !ruby/object:Gem::Version
166
+ version: '0'
153
167
  - !ruby/object:Gem::Dependency
154
168
  name: effective_test_bot
155
169
  requirement: !ruby/object:Gem::Requirement
@@ -195,10 +209,12 @@ files:
195
209
  - app/assets/stylesheets/effective_messaging/base.scss
196
210
  - app/controllers/admin/chat_messages_controller.rb
197
211
  - app/controllers/admin/chats_controller.rb
212
+ - app/controllers/admin/notification_logs_controller.rb
198
213
  - app/controllers/admin/notifications_controller.rb
199
214
  - app/controllers/effective/chats_controller.rb
200
215
  - app/datatables/admin/effective_chat_messages_datatable.rb
201
216
  - app/datatables/admin/effective_chats_datatable.rb
217
+ - app/datatables/admin/effective_notification_logs_datatable.rb
202
218
  - app/datatables/admin/effective_notifications_datatable.rb
203
219
  - app/datatables/effective_chats_datatable.rb
204
220
  - app/helpers/effective_messaging_helper.rb
@@ -211,11 +227,13 @@ files:
211
227
  - app/models/effective/chat_message.rb
212
228
  - app/models/effective/chat_user.rb
213
229
  - app/models/effective/notification.rb
230
+ - app/models/effective/notification_log.rb
214
231
  - app/views/admin/chat_messages/_chat_message.html.haml
215
232
  - app/views/admin/chats/_chat.html.haml
216
233
  - app/views/admin/chats/_form.html.haml
217
234
  - app/views/admin/chats/_form_chat.html.haml
218
235
  - app/views/admin/chats/_layout.html.haml
236
+ - app/views/admin/notification_logs/_notification_log.html.haml
219
237
  - app/views/admin/notifications/_form.html.haml
220
238
  - app/views/admin/notifications/_form_notification.html.haml
221
239
  - app/views/admin/notifications/_notification.html.haml