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 +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/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
|