activity_notification 2.4.0 → 2.5.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.
Files changed (253) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +25 -29
  3. data/app/jobs/activity_notification/cascading_notification_job.rb +123 -0
  4. data/docs/Functions.md +202 -12
  5. data/docs/Setup.md +34 -2
  6. data/docs/Testing.md +12 -1
  7. data/lib/activity_notification/apis/cascading_notification_api.rb +208 -0
  8. data/lib/activity_notification/apis/notification_api.rb +3 -0
  9. data/lib/activity_notification/config.rb +10 -0
  10. data/lib/activity_notification/mailers/helpers.rb +27 -1
  11. data/lib/activity_notification/models/concerns/swagger/subscription_schema.rb +1 -1
  12. data/lib/activity_notification/version.rb +1 -1
  13. data/lib/generators/templates/activity_notification.rb +8 -0
  14. metadata +45 -481
  15. data/.codeclimate.yml +0 -33
  16. data/.coveralls.yml +0 -1
  17. data/.github/ISSUE_TEMPLATE/bug_report.md +0 -22
  18. data/.github/ISSUE_TEMPLATE/feature_request.md +0 -17
  19. data/.github/pull_request_template.md +0 -13
  20. data/.github/workflows/build.yml +0 -101
  21. data/.gitignore +0 -74
  22. data/.rspec +0 -3
  23. data/.rubocop.yml +0 -1157
  24. data/.yardopts +0 -6
  25. data/CHANGELOG.md +0 -439
  26. data/Gemfile +0 -31
  27. data/Procfile +0 -2
  28. data/Rakefile +0 -28
  29. data/activity_notification.gemspec +0 -44
  30. data/ai-curated-specs/issues/172/design.md +0 -220
  31. data/ai-curated-specs/issues/172/tasks.md +0 -326
  32. data/ai-curated-specs/issues/188/design.md +0 -227
  33. data/ai-curated-specs/issues/188/requirements.md +0 -78
  34. data/ai-curated-specs/issues/188/tasks.md +0 -203
  35. data/ai-curated-specs/issues/188/upstream-contributions.md +0 -592
  36. data/ai-curated-specs/issues/50/design.md +0 -235
  37. data/ai-curated-specs/issues/50/requirements.md +0 -49
  38. data/ai-curated-specs/issues/50/tasks.md +0 -232
  39. data/bin/_dynamodblocal +0 -4
  40. data/bin/bundle_update.sh +0 -7
  41. data/bin/deploy_on_heroku.sh +0 -16
  42. data/bin/install_dynamodblocal.sh +0 -5
  43. data/bin/start_dynamodblocal.sh +0 -47
  44. data/bin/stop_dynamodblocal.sh +0 -34
  45. data/gemfiles/Gemfile.rails-5.0 +0 -25
  46. data/gemfiles/Gemfile.rails-5.1 +0 -25
  47. data/gemfiles/Gemfile.rails-5.2 +0 -24
  48. data/gemfiles/Gemfile.rails-6.0 +0 -23
  49. data/gemfiles/Gemfile.rails-6.1 +0 -22
  50. data/gemfiles/Gemfile.rails-7.0 +0 -25
  51. data/gemfiles/Gemfile.rails-7.1 +0 -23
  52. data/gemfiles/Gemfile.rails-7.2 +0 -23
  53. data/gemfiles/Gemfile.rails-8.0 +0 -24
  54. data/package.json +0 -8
  55. data/spec/channels/notification_api_channel_shared_examples.rb +0 -59
  56. data/spec/channels/notification_api_channel_spec.rb +0 -49
  57. data/spec/channels/notification_api_with_devise_channel_spec.rb +0 -76
  58. data/spec/channels/notification_channel_shared_examples.rb +0 -59
  59. data/spec/channels/notification_channel_spec.rb +0 -48
  60. data/spec/channels/notification_with_devise_channel_spec.rb +0 -97
  61. data/spec/concerns/apis/notification_api_spec.rb +0 -1627
  62. data/spec/concerns/apis/subscription_api_spec.rb +0 -474
  63. data/spec/concerns/common_spec.rb +0 -213
  64. data/spec/concerns/models/group_spec.rb +0 -61
  65. data/spec/concerns/models/notifiable_spec.rb +0 -782
  66. data/spec/concerns/models/notifier_spec.rb +0 -71
  67. data/spec/concerns/models/subscriber_spec.rb +0 -800
  68. data/spec/concerns/models/target_spec.rb +0 -1285
  69. data/spec/concerns/renderable_spec.rb +0 -129
  70. data/spec/config_spec.rb +0 -85
  71. data/spec/controllers/common_controller_spec.rb +0 -25
  72. data/spec/controllers/controller_spec_utility.rb +0 -100
  73. data/spec/controllers/dummy_common_controller.rb +0 -5
  74. data/spec/controllers/notifications_api_controller_shared_examples.rb +0 -619
  75. data/spec/controllers/notifications_api_controller_spec.rb +0 -19
  76. data/spec/controllers/notifications_api_with_devise_controller_spec.rb +0 -60
  77. data/spec/controllers/notifications_controller_shared_examples.rb +0 -743
  78. data/spec/controllers/notifications_controller_spec.rb +0 -11
  79. data/spec/controllers/notifications_with_devise_controller_spec.rb +0 -97
  80. data/spec/controllers/subscriptions_api_controller_shared_examples.rb +0 -750
  81. data/spec/controllers/subscriptions_api_controller_spec.rb +0 -19
  82. data/spec/controllers/subscriptions_api_with_devise_controller_spec.rb +0 -60
  83. data/spec/controllers/subscriptions_controller_shared_examples.rb +0 -946
  84. data/spec/controllers/subscriptions_controller_spec.rb +0 -11
  85. data/spec/controllers/subscriptions_with_devise_controller_spec.rb +0 -97
  86. data/spec/factories/admins.rb +0 -5
  87. data/spec/factories/articles.rb +0 -5
  88. data/spec/factories/comments.rb +0 -6
  89. data/spec/factories/dummy/dummy_group.rb +0 -4
  90. data/spec/factories/dummy/dummy_notifiable.rb +0 -4
  91. data/spec/factories/dummy/dummy_notifier.rb +0 -4
  92. data/spec/factories/dummy/dummy_subscriber.rb +0 -4
  93. data/spec/factories/dummy/dummy_target.rb +0 -4
  94. data/spec/factories/notifications.rb +0 -7
  95. data/spec/factories/subscriptions.rb +0 -8
  96. data/spec/factories/users.rb +0 -11
  97. data/spec/generators/controllers_generator_spec.rb +0 -85
  98. data/spec/generators/install_generator_spec.rb +0 -43
  99. data/spec/generators/migration/migration_generator_spec.rb +0 -66
  100. data/spec/generators/models_generator_spec.rb +0 -96
  101. data/spec/generators/views_generator_spec.rb +0 -195
  102. data/spec/helpers/polymorphic_helpers_spec.rb +0 -89
  103. data/spec/helpers/view_helpers_spec.rb +0 -547
  104. data/spec/jobs/notification_resilience_job_spec.rb +0 -167
  105. data/spec/jobs/notify_all_job_spec.rb +0 -23
  106. data/spec/jobs/notify_job_spec.rb +0 -23
  107. data/spec/jobs/notify_to_job_spec.rb +0 -23
  108. data/spec/mailers/mailer_spec.rb +0 -214
  109. data/spec/mailers/notification_resilience_spec.rb +0 -263
  110. data/spec/models/dummy/dummy_group_spec.rb +0 -10
  111. data/spec/models/dummy/dummy_notifiable_spec.rb +0 -10
  112. data/spec/models/dummy/dummy_notifier_spec.rb +0 -10
  113. data/spec/models/dummy/dummy_subscriber_spec.rb +0 -8
  114. data/spec/models/dummy/dummy_target_spec.rb +0 -10
  115. data/spec/models/notification_spec.rb +0 -472
  116. data/spec/models/subscription_spec.rb +0 -215
  117. data/spec/optional_targets/action_cable_api_channel_spec.rb +0 -34
  118. data/spec/optional_targets/action_cable_channel_spec.rb +0 -41
  119. data/spec/optional_targets/amazon_sns_spec.rb +0 -47
  120. data/spec/optional_targets/base_spec.rb +0 -45
  121. data/spec/optional_targets/slack_spec.rb +0 -44
  122. data/spec/orm/dynamoid_spec.rb +0 -115
  123. data/spec/rails_app/Rakefile +0 -15
  124. data/spec/rails_app/app/assets/config/manifest.js +0 -3
  125. data/spec/rails_app/app/assets/images/.keep +0 -0
  126. data/spec/rails_app/app/assets/javascripts/application.js +0 -3
  127. data/spec/rails_app/app/assets/javascripts/cable.js +0 -12
  128. data/spec/rails_app/app/assets/stylesheets/application.css +0 -15
  129. data/spec/rails_app/app/assets/stylesheets/reset.css +0 -85
  130. data/spec/rails_app/app/assets/stylesheets/style.css +0 -244
  131. data/spec/rails_app/app/controllers/admins_controller.rb +0 -21
  132. data/spec/rails_app/app/controllers/application_controller.rb +0 -5
  133. data/spec/rails_app/app/controllers/articles_controller.rb +0 -67
  134. data/spec/rails_app/app/controllers/comments_controller.rb +0 -36
  135. data/spec/rails_app/app/controllers/concerns/.keep +0 -0
  136. data/spec/rails_app/app/controllers/spa_controller.rb +0 -7
  137. data/spec/rails_app/app/controllers/users/notifications_controller.rb +0 -2
  138. data/spec/rails_app/app/controllers/users/notifications_with_devise_controller.rb +0 -2
  139. data/spec/rails_app/app/controllers/users/subscriptions_controller.rb +0 -2
  140. data/spec/rails_app/app/controllers/users/subscriptions_with_devise_controller.rb +0 -2
  141. data/spec/rails_app/app/controllers/users_controller.rb +0 -26
  142. data/spec/rails_app/app/helpers/application_helper.rb +0 -2
  143. data/spec/rails_app/app/helpers/devise_helper.rb +0 -2
  144. data/spec/rails_app/app/javascript/App.vue +0 -40
  145. data/spec/rails_app/app/javascript/components/DeviseTokenAuth.vue +0 -82
  146. data/spec/rails_app/app/javascript/components/Top.vue +0 -98
  147. data/spec/rails_app/app/javascript/components/notifications/Index.vue +0 -200
  148. data/spec/rails_app/app/javascript/components/notifications/Notification.vue +0 -133
  149. data/spec/rails_app/app/javascript/components/notifications/NotificationContent.vue +0 -122
  150. data/spec/rails_app/app/javascript/components/subscriptions/Index.vue +0 -279
  151. data/spec/rails_app/app/javascript/components/subscriptions/NewSubscription.vue +0 -112
  152. data/spec/rails_app/app/javascript/components/subscriptions/NotificationKey.vue +0 -141
  153. data/spec/rails_app/app/javascript/components/subscriptions/Subscription.vue +0 -226
  154. data/spec/rails_app/app/javascript/config/development.js +0 -5
  155. data/spec/rails_app/app/javascript/config/environment.js +0 -7
  156. data/spec/rails_app/app/javascript/config/production.js +0 -5
  157. data/spec/rails_app/app/javascript/config/test.js +0 -5
  158. data/spec/rails_app/app/javascript/packs/application.js +0 -18
  159. data/spec/rails_app/app/javascript/packs/spa.js +0 -14
  160. data/spec/rails_app/app/javascript/router/index.js +0 -73
  161. data/spec/rails_app/app/javascript/store/index.js +0 -37
  162. data/spec/rails_app/app/mailers/.keep +0 -0
  163. data/spec/rails_app/app/mailers/custom_notification_mailer.rb +0 -5
  164. data/spec/rails_app/app/models/admin.rb +0 -35
  165. data/spec/rails_app/app/models/article.rb +0 -54
  166. data/spec/rails_app/app/models/comment.rb +0 -81
  167. data/spec/rails_app/app/models/dummy/dummy_base.rb +0 -11
  168. data/spec/rails_app/app/models/dummy/dummy_group.rb +0 -23
  169. data/spec/rails_app/app/models/dummy/dummy_notifiable.rb +0 -15
  170. data/spec/rails_app/app/models/dummy/dummy_notifiable_target.rb +0 -27
  171. data/spec/rails_app/app/models/dummy/dummy_notifier.rb +0 -15
  172. data/spec/rails_app/app/models/dummy/dummy_subscriber.rb +0 -14
  173. data/spec/rails_app/app/models/dummy/dummy_target.rb +0 -16
  174. data/spec/rails_app/app/models/user.rb +0 -73
  175. data/spec/rails_app/app/views/activity_notification/mailer/dummy_subscribers/test_key.text.erb +0 -1
  176. data/spec/rails_app/app/views/activity_notification/notifications/default/article/_update.html.erb +0 -146
  177. data/spec/rails_app/app/views/activity_notification/notifications/default/custom/_path_test.html.erb +0 -1
  178. data/spec/rails_app/app/views/activity_notification/notifications/default/custom/_test.html.erb +0 -1
  179. data/spec/rails_app/app/views/activity_notification/notifications/users/_custom_index.html.erb +0 -1
  180. data/spec/rails_app/app/views/activity_notification/notifications/users/custom/_test.html.erb +0 -1
  181. data/spec/rails_app/app/views/activity_notification/notifications/users/overridden/custom/_test.html.erb +0 -1
  182. data/spec/rails_app/app/views/activity_notification/optional_targets/admins/amazon_sns/comment/_default.text.erb +0 -10
  183. data/spec/rails_app/app/views/articles/_form.html.erb +0 -24
  184. data/spec/rails_app/app/views/articles/edit.html.erb +0 -8
  185. data/spec/rails_app/app/views/articles/index.html.erb +0 -113
  186. data/spec/rails_app/app/views/articles/new.html.erb +0 -7
  187. data/spec/rails_app/app/views/articles/show.html.erb +0 -49
  188. data/spec/rails_app/app/views/layouts/_header.html.erb +0 -46
  189. data/spec/rails_app/app/views/layouts/application.html.erb +0 -15
  190. data/spec/rails_app/app/views/spa/index.html.erb +0 -2
  191. data/spec/rails_app/babel.config.js +0 -72
  192. data/spec/rails_app/bin/bundle +0 -3
  193. data/spec/rails_app/bin/rails +0 -4
  194. data/spec/rails_app/bin/rake +0 -4
  195. data/spec/rails_app/bin/setup +0 -29
  196. data/spec/rails_app/bin/webpack +0 -18
  197. data/spec/rails_app/bin/webpack-dev-server +0 -18
  198. data/spec/rails_app/config/application.rb +0 -54
  199. data/spec/rails_app/config/boot.rb +0 -5
  200. data/spec/rails_app/config/cable.yml +0 -8
  201. data/spec/rails_app/config/database.yml +0 -36
  202. data/spec/rails_app/config/dynamoid.rb +0 -13
  203. data/spec/rails_app/config/environment.rb +0 -26
  204. data/spec/rails_app/config/environments/development.rb +0 -60
  205. data/spec/rails_app/config/environments/production.rb +0 -85
  206. data/spec/rails_app/config/environments/test.rb +0 -53
  207. data/spec/rails_app/config/initializers/activity_notification.rb +0 -104
  208. data/spec/rails_app/config/initializers/assets.rb +0 -11
  209. data/spec/rails_app/config/initializers/backtrace_silencers.rb +0 -7
  210. data/spec/rails_app/config/initializers/cookies_serializer.rb +0 -3
  211. data/spec/rails_app/config/initializers/copy_it.aws.rb.template +0 -6
  212. data/spec/rails_app/config/initializers/devise.rb +0 -278
  213. data/spec/rails_app/config/initializers/devise_token_auth.rb +0 -55
  214. data/spec/rails_app/config/initializers/filter_parameter_logging.rb +0 -4
  215. data/spec/rails_app/config/initializers/inflections.rb +0 -16
  216. data/spec/rails_app/config/initializers/mime_types.rb +0 -4
  217. data/spec/rails_app/config/initializers/mysql.rb +0 -9
  218. data/spec/rails_app/config/initializers/session_store.rb +0 -3
  219. data/spec/rails_app/config/initializers/wrap_parameters.rb +0 -14
  220. data/spec/rails_app/config/initializers/zeitwerk.rb +0 -10
  221. data/spec/rails_app/config/locales/activity_notification.en.yml +0 -26
  222. data/spec/rails_app/config/locales/devise.en.yml +0 -62
  223. data/spec/rails_app/config/mongoid.yml +0 -13
  224. data/spec/rails_app/config/routes.rb +0 -50
  225. data/spec/rails_app/config/secrets.yml +0 -22
  226. data/spec/rails_app/config/webpack/development.js +0 -5
  227. data/spec/rails_app/config/webpack/environment.js +0 -7
  228. data/spec/rails_app/config/webpack/loaders/vue.js +0 -6
  229. data/spec/rails_app/config/webpack/production.js +0 -5
  230. data/spec/rails_app/config/webpack/test.js +0 -5
  231. data/spec/rails_app/config/webpacker.yml +0 -97
  232. data/spec/rails_app/config.ru +0 -4
  233. data/spec/rails_app/db/migrate/20160716000000_create_test_tables.rb +0 -42
  234. data/spec/rails_app/db/migrate/20181209000000_create_activity_notification_tables.rb +0 -33
  235. data/spec/rails_app/db/migrate/20191201000000_add_tokens_to_users.rb +0 -10
  236. data/spec/rails_app/db/schema.rb +0 -98
  237. data/spec/rails_app/db/seeds.rb +0 -95
  238. data/spec/rails_app/lib/custom_optional_targets/console_output.rb +0 -16
  239. data/spec/rails_app/lib/custom_optional_targets/raise_error.rb +0 -14
  240. data/spec/rails_app/lib/custom_optional_targets/wrong_target.rb +0 -13
  241. data/spec/rails_app/lib/mailer_previews/mailer_preview.rb +0 -29
  242. data/spec/rails_app/package.json +0 -23
  243. data/spec/rails_app/postcss.config.js +0 -12
  244. data/spec/rails_app/public/404.html +0 -67
  245. data/spec/rails_app/public/422.html +0 -67
  246. data/spec/rails_app/public/500.html +0 -66
  247. data/spec/rails_app/public/favicon.ico +0 -0
  248. data/spec/roles/acts_as_group_spec.rb +0 -30
  249. data/spec/roles/acts_as_notifiable_spec.rb +0 -432
  250. data/spec/roles/acts_as_notifier_spec.rb +0 -30
  251. data/spec/roles/acts_as_target_spec.rb +0 -36
  252. data/spec/spec_helper.rb +0 -56
  253. data/spec/version_spec.rb +0 -31
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0f81f1308188c8ac3e07332a40b1fab62c5af3a43dbbe135a71303e31d403285
4
- data.tar.gz: db55d9f9811dcceecd1f562feaf33949faf716ce6ab07e45f0f35b077b032923
3
+ metadata.gz: 7ac9e17e63d801b74604936ea099825e1eedd68c5f743b35c01e7af9e73d90ab
4
+ data.tar.gz: 07db03884cf03f86db283a7b5cbd92f7629277ea5f4f5a910629924471ff62e3
5
5
  SHA512:
6
- metadata.gz: b4287ae97e4d9f3f3788a5d0fc36b9eb9837880d1ed4289a2e881d32e8a61baecfcd9dc1f9f8e0846214a87fdd4a808e042a3ae77a283a2eb5d4d4bffa4f6411
7
- data.tar.gz: 2be55085563ca99a32c029bcc5ecf5e3a025d42a72326ef0f40f218f123af478d2adf760bf5cb51a142a6e326d0bf6c33374c9116e2cd39f0d1b248d3e69b5bd
6
+ metadata.gz: 91e6206957623ce19971e581afa07fe3b464b02273a968c17bbf145da5836aecbd9b0919b05fae844f912a003a9eeb0d2d4a1df011889ba6bcfb7d752c632346
7
+ data.tar.gz: 0efe5572854a47c271f6b79b1e0ad649b0d491c1ba4b5370a09a78a0876b4f684bf841069fbaba26fff24e3735ae6b11eeb9bf92ce75f359776d59fd526519ce
data/README.md CHANGED
@@ -24,6 +24,7 @@
24
24
  * Grouping notifications (grouping like *"Kevin and 7 other users posted comments to this article"*)
25
25
  * Email notification
26
26
  * Batch email notification (event driven or periodical email notification, daily or weekly etc)
27
+ * Cascading notifications (progressive notification escalation through multiple channels with time delays)
27
28
  * Push notification with [Action Cable](https://guides.rubyonrails.org/action_cable_overview.html)
28
29
  * Subscription management (subscribing and unsubscribing for each target and notification type)
29
30
  * REST API backend and [OpenAPI Specification](https://github.com/OAI/OpenAPI-Specification)
@@ -31,22 +32,6 @@
31
32
  * Activity notifications stream integrated into cloud computing using [Amazon DynamoDB Streams](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Streams.html)
32
33
  * Optional notification targets (Configurable optional notification targets like [Amazon SNS](https://aws.amazon.com/sns), [Slack](https://slack.com), SMS and so on)
33
34
 
34
- ### Online Demo
35
-
36
- You can see an actual application using this gem here:
37
- * **https://activity-notification-example.herokuapp.com/**
38
-
39
- Login as the following test users to experience user activity notifications:
40
-
41
- | Email | Password | Admin? |
42
- |:---:|:---:|:---:|
43
- | ichiro@example.com | changeit | Yes |
44
- | stephen@example.com | changeit | |
45
- | klay@example.com | changeit | |
46
- | kevin@example.com | changeit | |
47
-
48
- The deployed demo application is included in this gem's source code as a test application here: *[/spec/rails_app](/spec/rails_app/)*
49
-
50
35
  ### Notification index and plugin notifications
51
36
 
52
37
  <kbd>![plugin-notifications-image](https://raw.githubusercontent.com/simukappu/activity_notification/images/activity_notification_plugin_focus_with_subscription.png)</kbd>
@@ -70,16 +55,12 @@ The deployed demo application is included in this gem's source code as a test ap
70
55
  REST API reference as OpenAPI Specification is published in SwaggerHub here:
71
56
  * **https://app.swaggerhub.com/apis-docs/simukappu/activity-notification/**
72
57
 
73
- You can see sample single page application using [Vue.js](https://vuejs.org) as a part of example Rails application here:
74
- * **https://activity-notification-example.herokuapp.com/spa/**
75
-
76
- This sample application works with *activity_notification* REST API backend.
58
+ You can see sample single page application using [Vue.js](https://vuejs.org) as a part of example Rails application in *[/spec/rails_app](/spec/rails_app/)*. This sample application works with *activity_notification* REST API backend.
77
59
 
78
60
 
79
61
  ## Table of Contents
80
62
 
81
63
  - [About](#about)
82
- - [Online Demo](#online-demo)
83
64
  - [Public REST API reference as OpenAPI Specification](#public-rest-apu-reference-as-openapi-specification)
84
65
  - [Getting Started](#getting-started)
85
66
  - [Setup](/docs/Setup.md#Setup)
@@ -124,6 +105,7 @@ This sample application works with *activity_notification* REST API backend.
124
105
  - [Batch email subject](/docs/Functions.md#batch-email-subject)
125
106
  - [i18n for batch email](/docs/Functions.md#i18n-for-batch-email)
126
107
  - [Grouping notifications](/docs/Functions.md#grouping-notifications)
108
+ - [Cascading notifications](/docs/Functions.md#cascading-notifications)
127
109
  - [Subscription management](/docs/Functions.md#subscription-management)
128
110
  - [Configuring subscriptions](/docs/Functions.md#configuring-subscriptions)
129
111
  - [Managing subscriptions](/docs/Functions.md#managing-subscriptions)
@@ -155,6 +137,7 @@ This sample application works with *activity_notification* REST API backend.
155
137
  - [Testing gem alone](/docs/Testing.md#testing-gem-alone)
156
138
  - [Documentation](#documentation)
157
139
  - [Common Examples](#common-examples)
140
+ - [Example Rails application](/docs/Testing.md#example-rails-application)
158
141
  - [Contributing](#contributing)
159
142
  - [License](#license)
160
143
 
@@ -184,6 +167,24 @@ $ bin/rails generate activity_notification:install
184
167
 
185
168
  The generator will install an initializer which describes all configuration options of *activity_notification*.
186
169
 
170
+ #### ORM Dependencies
171
+
172
+ By default, *activity_notification* uses **ActiveRecord** as the ORM and no additional ORM gems are required.
173
+
174
+ If you intend to use **Mongoid** support, you need to add the `mongoid` gem separately to your Gemfile:
175
+
176
+ ```ruby
177
+ gem 'activity_notification'
178
+ gem 'mongoid', '>= 4.0.0', '< 10.0'
179
+ ```
180
+
181
+ If you intend to use **Dynamoid** support for Amazon DynamoDB, you need to add the `dynamoid` gem separately to your Gemfile:
182
+
183
+ ```ruby
184
+ gem 'activity_notification'
185
+ gem 'dynamoid', '>= 3.11.0', '< 4.0'
186
+ ```
187
+
187
188
  ### Database setup
188
189
 
189
190
  When you use *activity_notification* with ActiveRecord ORM as default configuration,
@@ -326,9 +327,9 @@ See [Testing](/docs/Testing.md#Testing).
326
327
 
327
328
  ## Documentation
328
329
 
329
- See [API Reference](http://www.rubydoc.info/github/simukappu/activity_notification/index) for more details.
330
+ `docs/` contains documentation for users to read. These files are included in the distributed Gem. `ai-docs/` contains AI-generated and design documents. These files are not included in the distributed Gem.
330
331
 
331
- RubyDoc.info does not support parsing methods in *included* and *class_methods* of *ActiveSupport::Concern* currently.
332
+ See [API Reference](http://www.rubydoc.info/github/simukappu/activity_notification/index) for more details. RubyDoc.info does not support parsing methods in *included* and *class_methods* of *ActiveSupport::Concern* currently.
332
333
  To read complete documents, please generate YARD documents on your local environment:
333
334
  ```console
334
335
  $ git pull https://github.com/simukappu/activity_notification.git
@@ -342,12 +343,7 @@ Then you can see the documents at <http://localhost:8808/docs/index>.
342
343
 
343
344
  ## Common Examples
344
345
 
345
- See example Rails application in *[/spec/rails_app](/spec/rails_app)*.
346
-
347
- You can also try this example Rails application as Online Demo here:
348
- * **https://activity-notification-example.herokuapp.com/**
349
-
350
- You can login as test users to experience user activity notifications. For more details, see [Online Demo](#online-demo).
346
+ See example Rails application in *[/spec/rails_app](/spec/rails_app)*. You can login as test users to experience user activity notifications. For more details, see [Example Rails application](/docs/Testing.md#example-rails-application).
351
347
 
352
348
 
353
349
  ## Contributing
@@ -0,0 +1,123 @@
1
+ if defined?(ActiveJob)
2
+ # Job to handle cascading notifications with time delays and read status checking.
3
+ # This job enables sequential delivery of notifications through different channels
4
+ # based on whether previous notifications were read.
5
+ #
6
+ # @example Basic usage
7
+ # cascade_config = [
8
+ # { delay: 10.minutes, target: :slack },
9
+ # { delay: 10.minutes, target: :email }
10
+ # ]
11
+ # CascadingNotificationJob.perform_later(notification.id, cascade_config, 0)
12
+ class ActivityNotification::CascadingNotificationJob < ActivityNotification.config.parent_job.constantize
13
+ queue_as ActivityNotification.config.active_job_queue
14
+
15
+ # Performs a single step in the cascading notification chain.
16
+ # Checks if the notification is still unread, and if so, triggers the next optional target
17
+ # and schedules the next step in the cascade.
18
+ #
19
+ # @param [Integer] notification_id ID of the notification to check
20
+ # @param [Array<Hash>] cascade_config Array of cascade step configurations
21
+ # @option cascade_config [ActiveSupport::Duration] :delay Time to wait before checking and sending
22
+ # @option cascade_config [Symbol, String] :target Name of the optional target to trigger (e.g., :slack, :email)
23
+ # @option cascade_config [Hash] :options Optional parameters to pass to the optional target
24
+ # @param [Integer] step_index Current step index in the cascade chain (0-based)
25
+ # @return [Hash, nil] Result of triggering the optional target, or nil if notification was read or not found
26
+ def perform(notification_id, cascade_config, step_index = 0)
27
+ # Find the notification using ORM-appropriate method
28
+ # :nocov:
29
+ notification = case ActivityNotification.config.orm
30
+ when :dynamoid
31
+ ActivityNotification::Notification.find(notification_id, raise_error: false)
32
+ when :mongoid
33
+ begin
34
+ ActivityNotification::Notification.find(notification_id)
35
+ rescue Mongoid::Errors::DocumentNotFound
36
+ nil
37
+ end
38
+ else
39
+ ActivityNotification::Notification.find_by(id: notification_id)
40
+ end
41
+ # :nocov:
42
+
43
+ # Return early if notification not found or has been opened (read)
44
+ return nil if notification.nil? || notification.opened?
45
+
46
+ # Get current step configuration
47
+ current_step = cascade_config[step_index]
48
+ return nil if current_step.nil?
49
+
50
+ # Extract step parameters
51
+ target_name = current_step[:target] || current_step['target']
52
+ target_options = current_step[:options] || current_step['options'] || {}
53
+
54
+ # Trigger the optional target for this step
55
+ result = trigger_optional_target(notification, target_name, target_options)
56
+
57
+ # Schedule next step if available and notification is still unread
58
+ next_step_index = step_index + 1
59
+ if next_step_index < cascade_config.length
60
+ next_step = cascade_config[next_step_index]
61
+ delay = next_step[:delay] || next_step['delay']
62
+
63
+ if delay.present?
64
+ # Schedule the next step with the specified delay
65
+ self.class.set(wait: delay).perform_later(
66
+ notification_id,
67
+ cascade_config,
68
+ next_step_index
69
+ )
70
+ end
71
+ end
72
+
73
+ result
74
+ end
75
+
76
+ private
77
+
78
+ # Triggers a specific optional target for the notification
79
+ # @param [Notification] notification The notification instance
80
+ # @param [Symbol, String] target_name Name of the optional target
81
+ # @param [Hash] options Options to pass to the optional target
82
+ # @return [Hash] Result of triggering the target
83
+ def trigger_optional_target(notification, target_name, options = {})
84
+ target_name_sym = target_name.to_sym
85
+
86
+ # Get all configured optional targets for this notification
87
+ optional_targets = notification.notifiable.optional_targets(
88
+ notification.target.to_resources_name,
89
+ notification.key
90
+ )
91
+
92
+ # Find the matching optional target
93
+ optional_target = optional_targets.find do |ot|
94
+ ot.to_optional_target_name == target_name_sym
95
+ end
96
+
97
+ if optional_target.nil?
98
+ Rails.logger.warn("Optional target '#{target_name}' not found for notification #{notification.id}")
99
+ return { target_name_sym => :not_configured }
100
+ end
101
+
102
+ # Check subscription status
103
+ unless notification.optional_target_subscribed?(target_name_sym)
104
+ Rails.logger.info("Target not subscribed to optional target '#{target_name}' for notification #{notification.id}")
105
+ return { target_name_sym => :not_subscribed }
106
+ end
107
+
108
+ # Trigger the optional target
109
+ begin
110
+ optional_target.notify(notification, options)
111
+ Rails.logger.info("Successfully triggered optional target '#{target_name}' for notification #{notification.id}")
112
+ { target_name_sym => :success }
113
+ rescue => e
114
+ Rails.logger.error("Failed to trigger optional target '#{target_name}' for notification #{notification.id}: #{e.message}")
115
+ if ActivityNotification.config.rescue_optional_target_errors
116
+ { target_name_sym => e }
117
+ else
118
+ raise e
119
+ end
120
+ end
121
+ end
122
+ end
123
+ end
data/docs/Functions.md CHANGED
@@ -99,7 +99,7 @@ If you use i18n for email, you can configure email subject in your locale files.
99
99
 
100
100
  #### Other header fields
101
101
 
102
- Similarly to the [Email subject](#email-subject), the `From`, `Reply-To` and `Message-ID` headers are configurable per notifiable model. From and reply to will override the `config.mailer_sender` config setting.
102
+ Similarly to the [Email subject](#email-subject), the `From`, `Reply-To`, `CC` and `Message-ID` headers are configurable per notifiable model. From and reply to will override the `config.mailer_sender` config setting.
103
103
 
104
104
  ```ruby
105
105
  class Comment < ActiveRecord::Base
@@ -120,12 +120,88 @@ class Comment < ActiveRecord::Base
120
120
  "no-reply.article+comment-#{self.id}@example.com"
121
121
  end
122
122
 
123
+ def overriding_notification_email_cc(target, key)
124
+ # CC the article author on comment notifications
125
+ if key == "comment.create"
126
+ article.user.email
127
+ else
128
+ nil
129
+ end
130
+ end
131
+
123
132
  def overriding_notification_email_message_id(target, key)
124
133
  "https://www.example.com/article/#{article.id}@example.com/"
125
134
  end
126
135
  end
127
136
  ```
128
137
 
138
+ #### CC (Carbon Copy) configuration
139
+
140
+ *activity_notification* supports CC (Carbon Copy) email addresses at three levels with the following priority order:
141
+
142
+ 1. **Notifiable model override** (highest priority) - using `overriding_notification_email_cc` method
143
+ 2. **Target model method** - using `mailer_cc` method
144
+ 3. **Global configuration** - using `config.mailer_cc` setting
145
+
146
+ ##### Global CC configuration
147
+
148
+ You can configure global CC recipients in *activity_notification.rb* initializer as *String*, *Array*, or *Proc*:
149
+
150
+ ```ruby
151
+ # Single CC recipient for all notifications
152
+ config.mailer_cc = 'admin@example.com'
153
+
154
+ # Multiple CC recipients for all notifications
155
+ config.mailer_cc = ['admin@example.com', 'support@example.com']
156
+
157
+ # Dynamic CC based on notification key
158
+ config.mailer_cc = ->(key) {
159
+ if key.include?('urgent')
160
+ ['urgent@example.com', 'manager@example.com']
161
+ else
162
+ 'admin@example.com'
163
+ end
164
+ }
165
+ ```
166
+
167
+ ##### Target-level CC configuration
168
+
169
+ You can define `mailer_cc` method in your target model to set CC recipients for that specific target:
170
+
171
+ ```ruby
172
+ class User < ActiveRecord::Base
173
+ acts_as_target
174
+ belongs_to :team_lead, class_name: 'User'
175
+
176
+ # Return single or multiple CC addresses
177
+ def mailer_cc
178
+ team_lead.present? ? team_lead.email : 'admin@example.com'
179
+ end
180
+ end
181
+ ```
182
+
183
+ ##### Notifiable-level CC override
184
+
185
+ For the most granular control, implement `overriding_notification_email_cc` in your notifiable model to set CC per notification type:
186
+
187
+ ```ruby
188
+ class Article < ActiveRecord::Base
189
+ acts_as_notifiable :users,
190
+ targets: ->(article, key) { [article.user] }
191
+
192
+ def overriding_notification_email_cc(target, key)
193
+ case key
194
+ when 'article.published'
195
+ ['editor@example.com', 'marketing@example.com']
196
+ when 'article.flagged'
197
+ 'moderation@example.com'
198
+ else
199
+ nil # Falls back to target's mailer_cc or global config
200
+ end
201
+ end
202
+ end
203
+ ```
204
+
129
205
  #### i18n for email
130
206
 
131
207
  The subject of notification email can be put in your locale *.yml* files as **mail_subject** field:
@@ -263,6 +339,126 @@ notification:
263
339
 
264
340
  Then, you will see *"Kevin and 7 other users posted 10 comments to your article"*.
265
341
 
342
+ ### Cascading notifications
343
+
344
+ *activity_notification* provides cascading notifications that enable progressive notification escalation through multiple channels with time delays. This ensures important notifications are not missed while avoiding unnecessary interruptions when users have already engaged with earlier notification channels.
345
+
346
+ #### How cascading notifications work
347
+
348
+ Cascading notifications automatically send notifications through different channels (Slack, Email, SMS, etc.) with configurable time delays, but only if the user hasn't already read the notification:
349
+
350
+ 1. User gets an in-app notification
351
+ 2. ⏱️ Wait 10 minutes → Still unread? Send Slack message
352
+ 3. ⏱️ Wait 10 more minutes → Still unread? Send Email
353
+ 4. ⏱️ Wait 30 more minutes → Still unread? Send SMS
354
+
355
+ If the user reads the notification at any point, the cascade stops automatically.
356
+
357
+ #### Basic usage
358
+
359
+ ```ruby
360
+ # Create a notification
361
+ notification = Notification.create!(
362
+ target: user,
363
+ notifiable: comment,
364
+ key: 'comment.reply'
365
+ )
366
+
367
+ # Setup cascade: Slack after 10 min, Email after another 10 min
368
+ cascade_config = [
369
+ { delay: 10.minutes, target: :slack },
370
+ { delay: 10.minutes, target: :email }
371
+ ]
372
+
373
+ # Start the cascade
374
+ notification.cascade_notify(cascade_config)
375
+ ```
376
+
377
+ #### Configuration options
378
+
379
+ Each step in the cascade requires:
380
+
381
+ | Parameter | Type | Required | Description |
382
+ |-----------|------|----------|-------------|
383
+ | `delay` | Duration | Yes | How long to wait (e.g., `10.minutes`, `1.hour`) |
384
+ | `target` | Symbol/String | Yes | Optional target name (`:slack`, `:email`, etc.) |
385
+ | `options` | Hash | No | Custom options to pass to the target |
386
+
387
+ #### Advanced usage
388
+
389
+ **Immediate first notification:**
390
+ ```ruby
391
+ # Send Slack immediately, then email if still unread
392
+ cascade_config = [
393
+ { delay: 5.minutes, target: :slack },
394
+ { delay: 10.minutes, target: :email }
395
+ ]
396
+
397
+ notification.cascade_notify(cascade_config, trigger_first_immediately: true)
398
+ ```
399
+
400
+ **With custom options:**
401
+ ```ruby
402
+ cascade_config = [
403
+ {
404
+ delay: 5.minutes,
405
+ target: :slack,
406
+ options: { channel: '#urgent' }
407
+ },
408
+ {
409
+ delay: 10.minutes,
410
+ target: :email
411
+ }
412
+ ]
413
+
414
+ notification.cascade_notify(cascade_config)
415
+ ```
416
+
417
+ **Integration with notification creation:**
418
+ ```ruby
419
+ # In your controller
420
+ comment = Comment.create!(comment_params)
421
+
422
+ # Create notifications
423
+ comment.notify(:users, key: 'comment.new')
424
+
425
+ # Add cascade to all created notifications
426
+ comment.notifications.each do |notification|
427
+ cascade_config = [
428
+ { delay: 10.minutes, target: :slack },
429
+ { delay: 30.minutes, target: :email }
430
+ ]
431
+ notification.cascade_notify(cascade_config)
432
+ end
433
+ ```
434
+
435
+ #### Prerequisites
436
+
437
+ Before using cascading notifications, ensure:
438
+
439
+ 1. **Optional targets are configured** on your notifiable models
440
+ 2. **ActiveJob is configured** (default in Rails)
441
+ 3. **Job queue is running** (Sidekiq, Delayed Job, etc.)
442
+
443
+ #### Common patterns
444
+
445
+ **Urgent notifications (fast escalation):**
446
+ ```ruby
447
+ URGENT_CASCADE = [
448
+ { delay: 2.minutes, target: :slack },
449
+ { delay: 5.minutes, target: :email },
450
+ { delay: 10.minutes, target: :sms }
451
+ ].freeze
452
+ ```
453
+
454
+ **Normal notifications (gentle escalation):**
455
+ ```ruby
456
+ NORMAL_CASCADE = [
457
+ { delay: 30.minutes, target: :slack },
458
+ { delay: 1.hour, target: :email }
459
+ ].freeze
460
+ ```
461
+
266
462
 
267
463
  ### Subscription management
268
464
 
@@ -381,8 +577,6 @@ end
381
577
 
382
578
  Then, you can access *users/1/subscriptions* and use *[ActivityNotification::SubscriptionsController](/app/controllers/activity_notification/subscriptions_controller.rb)* or *[ActivityNotification::SubscriptionsWithDeviseController](/app/controllers/activity_notification/subscriptions_with_devise_controller.rb)* to manage the subscriptions.
383
579
 
384
- You can see sample subscription management view in demo application here: *https://activity-notification-example.herokuapp.com/users/1/subscriptions*
385
-
386
580
  If you would like to customize subscription controllers or views, you can use generators like notifications:
387
581
 
388
582
  * Customize subscription controllers
@@ -436,8 +630,6 @@ You can see [sample single page application](/spec/rails_app/app/javascript/) us
436
630
 
437
631
  *activity_notification* provides API reference as [OpenAPI Specification](https://github.com/OAI/OpenAPI-Specification).
438
632
 
439
- OpenAPI Specification in [online demo](https://activity-notification-example.herokuapp.com/) is published here: **https://activity-notification-example.herokuapp.com/api/v2/apidocs**
440
-
441
633
  Public API reference is also hosted in [SwaggerHub](https://swagger.io/tools/swaggerhub/) here: **https://app.swaggerhub.com/apis-docs/simukappu/activity-notification/**
442
634
 
443
635
  You can also publish OpenAPI Specification in your own application using *[ActivityNotification::ApidocsController](/app/controllers/activity_notification/apidocs_controller.rb)* like this:
@@ -588,7 +780,7 @@ end
588
780
  To sign in and get *access-token* from Devise Token Auth, call *sign_in* API which you configured by *mount_devise_token_auth_for* method:
589
781
 
590
782
  ```console
591
- $ curl -X POST -H "Content-Type: application/json" -D - -d '{"email": "ichiro@example.com","password": "changeit"}' https://activity-notification-example.herokuapp.com/api/v2/auth/sign_in
783
+ $ curl -X POST -H "Content-Type: application/json" -D - -d '{"email": "ichiro@example.com","password": "changeit"}' https://localhost:3000/api/v2/auth/sign_in
592
784
 
593
785
 
594
786
  HTTP/1.1 200 OK
@@ -615,7 +807,7 @@ uid: ichiro@example.com
615
807
  Then, call *activity_notification* API with returned *access-token*, *client* and *uid* as HTTP headers:
616
808
 
617
809
  ```console
618
- $ curl -X GET -H "Content-Type: application/json" -H "access-token: ZiDvw8vJGtbESy5Qpw32Kw" -H "client: W0NkGrTS88xeOx4VDOS-Xg" -H "uid: ichiro@example.com" -D - https://activity-notification-example.herokuapp.com/api/v2/notifications
810
+ $ curl -X GET -H "Content-Type: application/json" -H "access-token: ZiDvw8vJGtbESy5Qpw32Kw" -H "client: W0NkGrTS88xeOx4VDOS-Xg" -H "uid: ichiro@example.com" -D - https://localhost:3000/api/v2/notifications
619
811
 
620
812
  HTTP/1.1 200 OK
621
813
  ...
@@ -631,7 +823,7 @@ HTTP/1.1 200 OK
631
823
  Without valid *access-token*, API returns *401 Unauthorized*:
632
824
 
633
825
  ```console
634
- $ curl -X GET -H "Content-Type: application/json" -D - https://activity-notification-example.herokuapp.com/api/v2/notifications
826
+ $ curl -X GET -H "Content-Type: application/json" -D - https://localhost:3000/api/v2/notifications
635
827
 
636
828
  HTTP/1.1 401 Unauthorized
637
829
  ...
@@ -646,7 +838,7 @@ HTTP/1.1 401 Unauthorized
646
838
  When you request restricted resources of unauthorized targets, *activity_notification* API returns *403 Forbidden*:
647
839
 
648
840
  ```console
649
- $ curl -X GET -H "Content-Type: application/json" -H "access-token: ZiDvw8vJGtbESy5Qpw32Kw" -H "client: W0NkGrTS88xeOx4VDOS-Xg" -H "uid: ichiro@example.com" -D - https://activity-notification-example.herokuapp.com/api/v2/notifications/1
841
+ $ curl -X GET -H "Content-Type: application/json" -H "access-token: ZiDvw8vJGtbESy5Qpw32Kw" -H "client: W0NkGrTS88xeOx4VDOS-Xg" -H "uid: ichiro@example.com" -D - https://localhost:3000/api/v2/notifications/1
650
842
 
651
843
  HTTP/1.1 403 Forbidden
652
844
  ...
@@ -1141,6 +1333,4 @@ user.find_or_create_subscription('comment.reply').subscribe_to_optional_target(:
1141
1333
  user.find_or_create_subscription('comment.reply').unsubscribe_to_optional_target(:slack)
1142
1334
  ```
1143
1335
 
1144
- You can also manage subscriptions of optional targets by subscriptions REST API. See [REST API backend](#rest-api-backend) for more details.
1145
-
1146
- You can see sample subscription management view in demo application here: *https://activity-notification-example.herokuapp.com/users/1/subscriptions*
1336
+ You can also manage subscriptions of optional targets by subscriptions REST API. See [REST API backend](#rest-api-backend) for more details.
data/docs/Setup.md CHANGED
@@ -22,6 +22,24 @@ $ bin/rails generate activity_notification:install
22
22
  The generator will install an initializer which describes all configuration options of *activity_notification*.
23
23
  It also generates a i18n based translation file which we can configure the presentation of notifications.
24
24
 
25
+ #### ORM Dependencies
26
+
27
+ By default, *activity_notification* uses **ActiveRecord** as the ORM and no additional ORM gems are required.
28
+
29
+ If you intend to use **Mongoid** support, you need to add the `mongoid` gem separately to your Gemfile:
30
+
31
+ ```ruby
32
+ gem 'activity_notification'
33
+ gem 'mongoid', '>= 4.0.0', '< 10.0'
34
+ ```
35
+
36
+ If you intend to use **Dynamoid** support for Amazon DynamoDB, you need to add the `dynamoid` gem separately to your Gemfile:
37
+
38
+ ```ruby
39
+ gem 'activity_notification'
40
+ gem 'dynamoid', '>= 3.11.0', '< 4.0'
41
+ ```
42
+
25
43
  ### Database setup
26
44
 
27
45
  #### Using ActiveRecord ORM
@@ -64,7 +82,14 @@ config.yaml_column_permitted_classes << Time
64
82
 
65
83
  #### Using Mongoid ORM
66
84
 
67
- When you use *activity_notification* with [Mongoid](http://mongoid.org) ORM, set **AN_ORM** environment variable to **mongoid**:
85
+ When you use *activity_notification* with [Mongoid](http://mongoid.org) ORM, you first need to add the `mongoid` gem to your Gemfile:
86
+
87
+ ```ruby
88
+ gem 'activity_notification'
89
+ gem 'mongoid', '>= 4.0.0', '< 10.0'
90
+ ```
91
+
92
+ Then set **AN_ORM** environment variable to **mongoid**:
68
93
 
69
94
  ```console
70
95
  $ export AN_ORM=mongoid
@@ -80,7 +105,14 @@ You need to configure Mongoid in your Rails application for your MongoDB environ
80
105
 
81
106
  #### Using Dynamoid ORM
82
107
 
83
- When you use *activity_notification* with [Dynamoid](https://github.com/Dynamoid/dynamoid) ORM, set **AN_ORM** environment variable to **dynamoid**:
108
+ When you use *activity_notification* with [Dynamoid](https://github.com/Dynamoid/dynamoid) ORM, you first need to add the `dynamoid` gem to your Gemfile:
109
+
110
+ ```ruby
111
+ gem 'activity_notification'
112
+ gem 'dynamoid', '>= 3.11.0', '< 4.0'
113
+ ```
114
+
115
+ Then set **AN_ORM** environment variable to **dynamoid**:
84
116
 
85
117
  ```console
86
118
  $ export AN_ORM=dynamoid
data/docs/Testing.md CHANGED
@@ -104,6 +104,17 @@ $ bin/rails server
104
104
  ```
105
105
  Then, you can access <http://localhost:3000> for the example application.
106
106
 
107
+ ##### Default test users
108
+
109
+ Login as the following test users to experience user activity notifications:
110
+
111
+ | Email | Password | Admin? |
112
+ |:---:|:---:|:---:|
113
+ | ichiro@example.com | changeit | Yes |
114
+ | stephen@example.com | changeit | |
115
+ | klay@example.com | changeit | |
116
+ | kevin@example.com | changeit | |
117
+
107
118
  ##### Run with your local database
108
119
  As default, example Rails application runs with local SQLite database in *spec/rails_app/db/development.sqlite3*.
109
120
  This application supports to run with your local MySQL, PostgreSQL, MongoDB.
@@ -144,5 +155,5 @@ $ cd spec/rails_app
144
155
  $ # You don't need migration when you use MongoDB only (AN_ORM=mongoid and AN_TEST_DB=mongodb)
145
156
  $ bin/rake db:migrate
146
157
  $ bin/rake db:seed
147
- $ bin/rails server Puma
158
+ $ bin/rails server
148
159
  ```