effective_messaging 0.12.3 → 0.13.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/README.md +20 -0
- data/app/controllers/admin/chat_messages_controller.rb +0 -1
- data/app/controllers/admin/chats_controller.rb +0 -1
- data/app/controllers/admin/notification_logs_controller.rb +0 -1
- data/app/controllers/admin/notifications_controller.rb +0 -1
- data/app/controllers/effective/chats_controller.rb +12 -2
- data/app/datatables/admin/effective_chat_messages_datatable.rb +0 -3
- data/app/datatables/admin/effective_chats_datatable.rb +0 -2
- data/app/datatables/effective_chats_datatable.rb +0 -1
- data/app/jobs/effective/notification_job.rb +0 -2
- data/app/mailers/effective/messaging_mailer.rb +0 -1
- data/app/mailers/effective/notifications_mailer.rb +0 -1
- data/app/models/concerns/effective_messaging_parent.rb +0 -1
- data/app/models/concerns/effective_messaging_user.rb +0 -1
- data/app/models/effective/chat.rb +2 -4
- data/app/models/effective/chat_message.rb +8 -1
- data/app/models/effective/chat_user.rb +1 -2
- data/app/models/effective/notification.rb +76 -6
- data/app/models/effective/notification_log.rb +0 -1
- data/app/views/admin/chats/_form.html.haml +1 -1
- data/app/views/admin/notifications/_form.html.haml +1 -1
- data/app/views/admin/notifications/_notification.html.haml +1 -1
- data/app/views/effective/chats/_chat_message.html.haml +3 -0
- data/app/views/effective/chats/_form.html.haml +2 -0
- data/config/effective_messaging.rb +4 -2
- data/config/routes.rb +3 -2
- data/db/migrate/101_create_effective_messaging.rb +0 -1
- data/lib/effective_messaging/engine.rb +0 -1
- data/lib/effective_messaging/version.rb +1 -1
- data/lib/effective_messaging.rb +1 -3
- data/lib/generators/effective_messaging/install_generator.rb +0 -1
- metadata +3 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b067851235561d03267380eeaf0e8c4f734c635e7c28689c0cc67830a4a84b6f
|
|
4
|
+
data.tar.gz: 9d21a71ec1dbc58dd46a174342c8eadd37eb0fb7c7042d1ed8106b785ba4a4fd
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 596dedbf28e9c3fc0cfa5f0d592f61d309992833a71c7c483e5ba3488dd7094626d77a7ce77833bd96203f116dfdd61c3eeceb160b28624cd7eeed184d777d53
|
|
7
|
+
data.tar.gz: faccebd0025d5fe3b2815d69d6ddafbec4a4aa5a7177b945c4c21decd9fa92b63192d220840f91194f22688e240e913ad0fa302048ce496ac6bd6c982f63ccea
|
data/README.md
CHANGED
|
@@ -4,6 +4,15 @@ Centralize all communications between one or more users.
|
|
|
4
4
|
|
|
5
5
|
Works with effective_reports to schedule notifications to report results containing emails.
|
|
6
6
|
|
|
7
|
+
`notification.delivery_status` provides a read-only forecast using the same `notifiable?`
|
|
8
|
+
checks as `notifiable_tomorrow_rows_count` and the sender. It returns `:next_on`, `:status`,
|
|
9
|
+
the last recorded send date, and counts of current recipients pending, finished, or blocked.
|
|
10
|
+
Resource notifications also respect same-day delivery logs and delivery errors. Skipped
|
|
11
|
+
logs count toward a course, as they do when sending. A completed course applies only to
|
|
12
|
+
the current report recipients; new matches may still receive notifications. Fixed-address
|
|
13
|
+
scheduled emails follow their configured dates even when the report is empty. This method
|
|
14
|
+
does not send email, write logs, or predict changes to the report audience or server cron.
|
|
15
|
+
|
|
7
16
|
## Getting Started
|
|
8
17
|
|
|
9
18
|
This requires Rails 6+ and Twitter Bootstrap 4 and just works with Devise.
|
|
@@ -142,3 +151,14 @@ rails test
|
|
|
142
151
|
4. Push to the branch (`git push origin my-new-feature`)
|
|
143
152
|
5. Bonus points for test coverage
|
|
144
153
|
6. Create new Pull Request
|
|
154
|
+
|
|
155
|
+
## Chat documents
|
|
156
|
+
|
|
157
|
+
Documents are disabled by default. Enable them in the host initializer:
|
|
158
|
+
|
|
159
|
+
```ruby
|
|
160
|
+
config.chat_attachments_enabled = true
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Messages still require text. Documents remain with their message and downloads
|
|
164
|
+
require permission to view the chat. Disabling the toggle hides documents and blocks downloads without deleting files. The host needs Active Storage configured and migrated.
|
|
@@ -6,11 +6,21 @@ module Effective
|
|
|
6
6
|
|
|
7
7
|
resource_scope -> { Effective::Chat.all.deep }
|
|
8
8
|
|
|
9
|
+
def download
|
|
10
|
+
raise ActiveRecord::RecordNotFound unless EffectiveMessaging.chat_attachments_enabled
|
|
11
|
+
chat = Effective::Chat.find(params[:id])
|
|
12
|
+
EffectiveResources.authorize!(self, :show, chat)
|
|
13
|
+
attachment = chat.chat_messages.find(params[:chat_message_id]).files.find(params[:file_id])
|
|
14
|
+
send_data attachment.download, filename: attachment.filename.to_s,
|
|
15
|
+
type: attachment.content_type, disposition: 'attachment'
|
|
16
|
+
end
|
|
17
|
+
|
|
9
18
|
private
|
|
10
19
|
|
|
11
20
|
def permitted_params
|
|
12
|
-
|
|
21
|
+
message_attributes = [:body]
|
|
22
|
+
message_attributes << { files: [] } if EffectiveMessaging.chat_attachments_enabled
|
|
23
|
+
params.require(:effective_chat).except(:user_type).permit(chat_messages_attributes: message_attributes)
|
|
13
24
|
end
|
|
14
|
-
|
|
15
25
|
end
|
|
16
26
|
end
|
|
@@ -34,8 +34,8 @@ module Effective
|
|
|
34
34
|
scope :sorted, -> { order(id: :desc) }
|
|
35
35
|
scope :deep, -> { includes(:chat_users, :chat_messages) }
|
|
36
36
|
|
|
37
|
-
scope :for_user, ->
|
|
38
|
-
scope :search_by_chat_user_name, ->
|
|
37
|
+
scope :for_user, ->(user) { where(id: ChatUser.where(user: user).select(:chat_id)) }
|
|
38
|
+
scope :search_by_chat_user_name, ->(name) { where(id: ChatUser.with_name(name).select(:chat_id)) }
|
|
39
39
|
|
|
40
40
|
validates :title, presence: true, length: { maximum: 255 }
|
|
41
41
|
|
|
@@ -65,7 +65,6 @@ module Effective
|
|
|
65
65
|
|
|
66
66
|
# Notify everyone in the chat except the user that created this message
|
|
67
67
|
notified = (chat_users - [except]).map do |chat_user|
|
|
68
|
-
|
|
69
68
|
# Only notify once every 5 minutes unless force
|
|
70
69
|
if chat_user.last_notified_at.present? && !force
|
|
71
70
|
next if (Time.zone.now - chat_user.last_notified_at) < NOTIFY_AFTER
|
|
@@ -130,6 +129,5 @@ module Effective
|
|
|
130
129
|
# Return users
|
|
131
130
|
users
|
|
132
131
|
end
|
|
133
|
-
|
|
134
132
|
end
|
|
135
133
|
end
|
|
@@ -6,6 +6,14 @@ module Effective
|
|
|
6
6
|
|
|
7
7
|
log_changes(to: :chat) if respond_to?(:log_changes)
|
|
8
8
|
|
|
9
|
+
has_many_attached :files
|
|
10
|
+
|
|
11
|
+
validate do
|
|
12
|
+
if attachment_changes['files'].present? && !EffectiveMessaging.chat_attachments_enabled
|
|
13
|
+
errors.add(:files, 'are disabled for chats')
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
9
17
|
belongs_to :chat, counter_cache: true
|
|
10
18
|
|
|
11
19
|
# Who sent this message
|
|
@@ -43,6 +51,5 @@ module Effective
|
|
|
43
51
|
def truncate(value)
|
|
44
52
|
value.length > 100 ? (value.first(100) + '...') : value
|
|
45
53
|
end
|
|
46
|
-
|
|
47
54
|
end
|
|
48
55
|
end
|
|
@@ -9,7 +9,7 @@ module Effective
|
|
|
9
9
|
belongs_to :chat
|
|
10
10
|
belongs_to :user, polymorphic: true
|
|
11
11
|
|
|
12
|
-
scope :with_name, ->
|
|
12
|
+
scope :with_name, ->(name) {
|
|
13
13
|
anonymous = where(chat_id: Chat.where(anonymous: true)).where('anonymous_name ILIKE ?', "%#{name}%")
|
|
14
14
|
displayed = where(chat_id: Chat.where(anonymous: false)).where('display_name ILIKE ?', "%#{name}%")
|
|
15
15
|
|
|
@@ -43,6 +43,5 @@ module Effective
|
|
|
43
43
|
def name
|
|
44
44
|
chat&.anonymous? ? anonymous_name : display_name
|
|
45
45
|
end
|
|
46
|
-
|
|
47
46
|
end
|
|
48
47
|
end
|
|
@@ -38,7 +38,7 @@ module Effective
|
|
|
38
38
|
|
|
39
39
|
# TODO: ['Send once', 'Send daily', 'Send weekly', 'Send monthly', 'Send quarterly', 'Send yearly', 'Send now']
|
|
40
40
|
SCHEDULED_METHODS = [
|
|
41
|
-
['The following dates...', 'dates']
|
|
41
|
+
['The following dates...', 'dates']
|
|
42
42
|
]
|
|
43
43
|
|
|
44
44
|
CONTENT_TYPES = ['text/plain', 'text/html']
|
|
@@ -208,6 +208,65 @@ module Effective
|
|
|
208
208
|
report.collection().select { |resource| notifiable_tomorrow?(resource) }.count if report
|
|
209
209
|
end
|
|
210
210
|
|
|
211
|
+
# Read-only forecast using the same eligibility checks as the sender.
|
|
212
|
+
# A completed immediate course applies to the current report recipients only.
|
|
213
|
+
def delivery_status(date: Time.zone.today)
|
|
214
|
+
return { status: :disabled, next_on: nil } unless enabled?
|
|
215
|
+
|
|
216
|
+
date = date.to_date
|
|
217
|
+
logs = notification_logs.to_a
|
|
218
|
+
result = { next_on: nil, last_sent_on: logs.reject(&:skipped?).map { |log| log.created_at&.to_date }.compact.max }
|
|
219
|
+
dates = nil
|
|
220
|
+
|
|
221
|
+
if scheduled?
|
|
222
|
+
dates = scheduled_dates.map { |value| Date.iso8601(value) rescue nil }.compact.uniq.sort
|
|
223
|
+
.select { |day| day >= date && notifiable_scheduled?(date: day) }
|
|
224
|
+
result[:next_scheduled_on] = dates.first
|
|
225
|
+
return result.merge(status: :finished) if dates.empty?
|
|
226
|
+
|
|
227
|
+
# These send once to the configured addresses, regardless of report membership.
|
|
228
|
+
# The sender does not deduplicate repeated task runs on the same scheduled day.
|
|
229
|
+
if scheduled_email?
|
|
230
|
+
status = dates.first == date ? :due : :scheduled
|
|
231
|
+
result[:sent_today] = result[:last_sent_on] == date
|
|
232
|
+
return result.merge(status: status, next_on: dates.first)
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
recipients = report.collection.find_each.group_by { |resource| resource_emails_to_s(resource) }
|
|
237
|
+
missing = recipients.delete(nil).to_a.size
|
|
238
|
+
logs_by_email = logs.group_by(&:email)
|
|
239
|
+
pending_dates = []
|
|
240
|
+
completed = blocked = 0
|
|
241
|
+
|
|
242
|
+
recipients.each do |email, resources|
|
|
243
|
+
# Duplicate email keys share a course; prefer a row without delivery errors.
|
|
244
|
+
resource = resources.find { |row| !email_delivery_error?(row) } || resources.first
|
|
245
|
+
next_on = next_delivery_on(resource, date: date, logs: logs_by_email.fetch(email, []), dates: dates)
|
|
246
|
+
if next_on.nil?
|
|
247
|
+
completed += 1
|
|
248
|
+
elsif email_delivery_error?(resource)
|
|
249
|
+
blocked += 1
|
|
250
|
+
else
|
|
251
|
+
pending_dates << next_on
|
|
252
|
+
end
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
next_on = pending_dates.min
|
|
256
|
+
status = if next_on
|
|
257
|
+
next_on == date ? :due : :scheduled
|
|
258
|
+
elsif blocked > 0 || missing > 0
|
|
259
|
+
:blocked
|
|
260
|
+
elsif completed > 0
|
|
261
|
+
:complete
|
|
262
|
+
else
|
|
263
|
+
:waiting
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
result.merge(status: status, next_on: next_on, pending_count: pending_dates.length,
|
|
267
|
+
completed_count: completed, blocked_count: blocked, missing_email_count: missing)
|
|
268
|
+
end
|
|
269
|
+
|
|
211
270
|
def enable!
|
|
212
271
|
update!(enabled: true)
|
|
213
272
|
end
|
|
@@ -327,11 +386,12 @@ module Effective
|
|
|
327
386
|
notified > 0 ? update!(last_notified_at: Time.zone.now, last_notified_count: notified) : touch
|
|
328
387
|
end
|
|
329
388
|
|
|
330
|
-
|
|
389
|
+
# Forecasts can supply logs already filtered to the resource's email key.
|
|
390
|
+
def notifiable?(resource, date: nil, logs: nil)
|
|
331
391
|
raise('expected an acts_as_reportable resource') unless resource.class.try(:acts_as_reportable?)
|
|
332
392
|
|
|
333
393
|
if schedule_type == 'immediate'
|
|
334
|
-
notifiable_immediate?(resource: resource, date: date)
|
|
394
|
+
notifiable_immediate?(resource: resource, date: date, logs: logs)
|
|
335
395
|
elsif schedule_type == 'scheduled'
|
|
336
396
|
notifiable_scheduled?(date: date)
|
|
337
397
|
else
|
|
@@ -357,13 +417,13 @@ module Effective
|
|
|
357
417
|
|
|
358
418
|
# Consider the notification logs which track how many and how long ago this notification was sent
|
|
359
419
|
# It's notifiable? when first time or if it's been immediate_days since last notification
|
|
360
|
-
def notifiable_immediate?(resource:, date: nil)
|
|
420
|
+
def notifiable_immediate?(resource:, date: nil, logs: nil)
|
|
361
421
|
raise('expected an immediate? notification') unless immediate?
|
|
362
422
|
|
|
363
423
|
email = resource_emails_to_s(resource)
|
|
364
424
|
return false if email.blank?
|
|
365
425
|
|
|
366
|
-
logs
|
|
426
|
+
logs ||= notification_logs.select { |log| log.email == email }
|
|
367
427
|
|
|
368
428
|
if logs.count == 0
|
|
369
429
|
true # This is the first time. We should send.
|
|
@@ -425,6 +485,17 @@ module Effective
|
|
|
425
485
|
|
|
426
486
|
private
|
|
427
487
|
|
|
488
|
+
def next_delivery_on(resource, date:, logs:, dates:)
|
|
489
|
+
unless dates
|
|
490
|
+
last_on = logs.map { |log| log.created_at&.to_date || date }.max
|
|
491
|
+
dates = [last_on ? [date, last_on + immediate_days].max : date]
|
|
492
|
+
end
|
|
493
|
+
|
|
494
|
+
dates.find do |day|
|
|
495
|
+
notifiable?(resource, date: day, logs: logs) && logs.none? { |log| log.created_at&.to_date == day }
|
|
496
|
+
end
|
|
497
|
+
end
|
|
498
|
+
|
|
428
499
|
def audience_emails_to_s
|
|
429
500
|
audience_emails.presence&.join(', ')
|
|
430
501
|
end
|
|
@@ -466,6 +537,5 @@ module Effective
|
|
|
466
537
|
|
|
467
538
|
resource.public_send(column.name)
|
|
468
539
|
end
|
|
469
|
-
|
|
470
540
|
end
|
|
471
541
|
end
|
|
@@ -7,3 +7,6 @@
|
|
|
7
7
|
.col.text-right= chat_message.created_at.strftime("%b %e, %Y %k:%M")
|
|
8
8
|
.card-body
|
|
9
9
|
= simple_format(chat_message.body)
|
|
10
|
+
- if EffectiveMessaging.chat_attachments_enabled
|
|
11
|
+
- chat_message.files.each do |file|
|
|
12
|
+
%p= link_to file.filename.to_s, effective_messaging.download_chat_path(chat_message.chat, chat_message_id: chat_message.id, file_id: file.id)
|
|
@@ -14,5 +14,7 @@
|
|
|
14
14
|
= f.fields_for :chat_messages, chat_message do |fcm|
|
|
15
15
|
- # The user and name are set by controller's current_user
|
|
16
16
|
= fcm.text_area :body, required: true, label: 'Message'
|
|
17
|
+
- if EffectiveMessaging.chat_attachments_enabled
|
|
18
|
+
= fcm.file_field :files, label: 'Documents'
|
|
17
19
|
|
|
18
20
|
= f.submit et('effective_messaging.send'), border: false
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
EffectiveMessaging.setup do |config|
|
|
2
|
+
# Allow documents on chat messages. Disabled unless explicitly enabled.
|
|
3
|
+
config.chat_attachments_enabled = false
|
|
4
|
+
|
|
2
5
|
# Layout Settings
|
|
3
6
|
# Configure the Layout per controller, or all at once
|
|
4
7
|
# config.layout = { application: 'application', admin: 'admin' }
|
|
5
8
|
|
|
6
9
|
# Mailer Settings
|
|
7
|
-
# Please see config/initializers/
|
|
10
|
+
# Please see config/initializers/effective_resources.rb for default effective_* gem mailer settings
|
|
8
11
|
#
|
|
9
12
|
# Configure the class responsible to send e-mails.
|
|
10
13
|
# config.mailer = 'Effective::MessagingMailer'
|
|
@@ -18,5 +21,4 @@ EffectiveMessaging.setup do |config|
|
|
|
18
21
|
# config.mailer_froms = nil # Default Froms collection
|
|
19
22
|
# config.mailer_admin = nil # Default To value for Admin correspondence
|
|
20
23
|
# config.mailer_subject = nil # Proc.new method used to customize Subject
|
|
21
|
-
|
|
22
24
|
end
|
data/config/routes.rb
CHANGED
|
@@ -7,7 +7,9 @@ end
|
|
|
7
7
|
EffectiveMessaging::Engine.routes.draw do
|
|
8
8
|
# Public routes
|
|
9
9
|
scope module: 'effective' do
|
|
10
|
-
resources :chats, only: [:show, :update]
|
|
10
|
+
resources :chats, only: [:show, :update] do
|
|
11
|
+
get :download, on: :member
|
|
12
|
+
end
|
|
11
13
|
end
|
|
12
14
|
|
|
13
15
|
namespace :admin do
|
|
@@ -24,5 +26,4 @@ EffectiveMessaging::Engine.routes.draw do
|
|
|
24
26
|
|
|
25
27
|
resources :notification_logs, only: [:index, :destroy]
|
|
26
28
|
end
|
|
27
|
-
|
|
28
29
|
end
|
data/lib/effective_messaging.rb
CHANGED
|
@@ -4,11 +4,10 @@ require 'effective_messaging/engine'
|
|
|
4
4
|
require 'effective_messaging/version'
|
|
5
5
|
|
|
6
6
|
module EffectiveMessaging
|
|
7
|
-
|
|
8
7
|
def self.config_keys
|
|
9
8
|
[
|
|
10
9
|
:chats_table_name, :chat_users_table_name, :chat_messages_table_name, :notifications_table_name, :notification_logs_table_name,
|
|
11
|
-
:layout,
|
|
10
|
+
:layout, :chat_attachments_enabled,
|
|
12
11
|
:mailer, :parent_mailer, :deliver_method, :mailer_layout, :mailer_sender, :mailer_froms, :mailer_admin, :mailer_subject
|
|
13
12
|
]
|
|
14
13
|
end
|
|
@@ -22,5 +21,4 @@ module EffectiveMessaging
|
|
|
22
21
|
def self.Notification
|
|
23
22
|
Effective::Notification
|
|
24
23
|
end
|
|
25
|
-
|
|
26
24
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: effective_messaging
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.13.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Code and Effect
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: bin
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
13
|
name: rails
|
|
@@ -276,7 +275,6 @@ homepage: https://github.com/code-and-effect/effective_messaging
|
|
|
276
275
|
licenses:
|
|
277
276
|
- MIT
|
|
278
277
|
metadata: {}
|
|
279
|
-
post_install_message:
|
|
280
278
|
rdoc_options: []
|
|
281
279
|
require_paths:
|
|
282
280
|
- lib
|
|
@@ -291,8 +289,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
291
289
|
- !ruby/object:Gem::Version
|
|
292
290
|
version: '0'
|
|
293
291
|
requirements: []
|
|
294
|
-
rubygems_version:
|
|
295
|
-
signing_key:
|
|
292
|
+
rubygems_version: 4.0.17
|
|
296
293
|
specification_version: 4
|
|
297
294
|
summary: Centralize the communication between one or more users.
|
|
298
295
|
test_files: []
|