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 +4 -4
- data/app/controllers/admin/notification_logs_controller.rb +15 -0
- data/app/controllers/admin/notifications_controller.rb +1 -1
- data/app/datatables/admin/effective_notification_logs_datatable.rb +22 -0
- data/app/datatables/admin/effective_notifications_datatable.rb +24 -7
- data/app/jobs/effective/notification_job.rb +2 -3
- data/app/mailers/effective/notifications_mailer.rb +47 -6
- data/app/models/effective/notification.rb +242 -45
- data/app/models/effective/notification_log.rb +34 -0
- data/app/views/admin/notification_logs/_notification_log.html.haml +1 -0
- data/app/views/admin/notifications/_form.html.haml +5 -1
- data/app/views/admin/notifications/_form_notification.html.haml +44 -23
- data/app/views/admin/notifications/_notification.html.haml +1 -25
- data/config/effective_messaging.rb +1 -0
- data/config/locales/effective_messaging.yml +2 -0
- data/config/routes.rb +1 -0
- data/db/migrate/01_create_effective_messaging.rb.erb +30 -4
- data/lib/effective_messaging/version.rb +1 -1
- data/lib/effective_messaging.rb +5 -1
- data/lib/generators/effective_messaging/install_generator.rb +1 -0
- data/lib/tasks/effective_messaging_tasks.rake +14 -12
- metadata +21 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ca64445b74c99e0eac989eb22c606a4fa588d05cbfefdd7401432266ab201b06
|
4
|
+
data.tar.gz: 0b25f92b272fd9f9903d7757a383983224de064ff9aae4dd6c56fa6b48187246
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
@@ -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 :
|
6
|
-
scope :
|
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 :
|
17
|
-
|
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 :
|
27
|
-
col :
|
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
|
@@ -2,11 +2,33 @@ module Effective
|
|
2
2
|
class NotificationsMailer < EffectiveMessaging.parent_mailer_class
|
3
3
|
include EffectiveMailer
|
4
4
|
|
5
|
-
|
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
|
-
|
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
|
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
|
-
|
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
|
-
#
|
34
|
-
|
35
|
-
|
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 :
|
45
|
-
scope :
|
46
|
-
|
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
|
-
#
|
49
|
-
|
104
|
+
# Scheduled
|
105
|
+
validates :scheduled_method, presence: true, inclusion: { in: SCHEDULED_METHODS.map(&:last) }, if: -> { scheduled? }
|
50
106
|
|
51
|
-
|
52
|
-
|
107
|
+
with_options(if: -> { scheduled_method.to_s == 'dates' }) do
|
108
|
+
validates :scheduled_dates, presence: true
|
53
109
|
end
|
54
110
|
|
55
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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 ||
|
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
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
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
|
-
|
105
|
-
|
209
|
+
# The main function
|
210
|
+
def notify!
|
211
|
+
scheduled_email? ? notify_by_schedule! : notify_by_resources!
|
106
212
|
end
|
107
213
|
|
108
|
-
|
109
|
-
|
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
|
-
|
122
|
-
|
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 (
|
231
|
+
GC.start if (notified % 250) == 0
|
125
232
|
end
|
126
233
|
|
127
|
-
update!(
|
234
|
+
notified > 0 ? update!(last_notified_at: Time.zone.now, last_notified_count: notified) : touch
|
128
235
|
end
|
129
236
|
|
130
|
-
|
131
|
-
|
132
|
-
update!(started_at: Time.zone.now, completed_at: nil, notifications_sent: nil)
|
237
|
+
def notify_by_schedule!
|
238
|
+
notified = 0
|
133
239
|
|
134
|
-
|
135
|
-
|
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
|
-
|
139
|
-
|
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
|
-
=
|
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
|
3
|
-
%p
|
4
|
-
|
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
|
-
|
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
|
-
.
|
11
|
-
|
12
|
-
|
13
|
-
|
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
|
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
|
-
|
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
|
-
|
24
|
-
|
25
|
-
|
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
|
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
|
57
|
-
= f.save 'Send Now', class: 'btn btn-warning', 'data-confirm': "Really send
|
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
|
-
|
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
@@ -49,7 +49,19 @@ class CreateEffectiveMessaging < ActiveRecord::Migration[6.0]
|
|
49
49
|
|
50
50
|
t.integer :report_id
|
51
51
|
|
52
|
-
t.
|
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 :
|
62
|
-
t.
|
63
|
-
|
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
|
data/lib/effective_messaging.rb
CHANGED
@@ -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:
|
1
|
+
# rake effective_messaging:send_notifications
|
2
2
|
namespace :effective_messaging do
|
3
3
|
desc 'Sends scheduled notifications for effective messaging'
|
4
|
-
task
|
4
|
+
task send_notifications: :environment do
|
5
5
|
puts 'Sending notifications'
|
6
6
|
|
7
|
-
|
7
|
+
table = ActiveRecord::Base.connection.table_exists?(:notifications)
|
8
|
+
blank_tenant = defined?(Tenant) && Tenant.current.blank?
|
8
9
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
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.
|
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-
|
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
|
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
|