effective_messaging 0.1.7 → 0.2.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: db18ef76924e1e79b7bb689ec41d693f920e045838c194a97c644dcc83c5d801
4
- data.tar.gz: be11de5dc4779d03356d0deee5f7f7c00688d452e0e67861b39cf61354c0c906
3
+ metadata.gz: ca64445b74c99e0eac989eb22c606a4fa588d05cbfefdd7401432266ab201b06
4
+ data.tar.gz: 0b25f92b272fd9f9903d7757a383983224de064ff9aae4dd6c56fa6b48187246
5
5
  SHA512:
6
- metadata.gz: f100cd5242657f441e5d6535254503b914b8382164ea94dd342b57d7876c44c63f694a15b3d9b64c6edef51f58a6a6e26cdec7482b5ed925f2bb3e232f904d16
7
- data.tar.gz: 55ad03d9d06b23c70defd737a2a047080944868ccc51c08494ba9feb50f401073297ef667db89fdf03bbfafec6034c9c9f282df008b1bbad861c351251a578bb
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
@@ -1,5 +1,7 @@
1
1
  en:
2
2
  effective_messaging:
3
+ name: 'Effective Messaging'
4
+ acronym: 'Messaging'
3
5
  dashboard: 'Chat Messages'
4
6
  send: 'Send Message'
5
7
 
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.7'.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.7
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-02-14 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