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.
- checksums.yaml +4 -4
- data/README.md +25 -29
- data/app/jobs/activity_notification/cascading_notification_job.rb +123 -0
- data/docs/Functions.md +202 -12
- data/docs/Setup.md +34 -2
- data/docs/Testing.md +12 -1
- data/lib/activity_notification/apis/cascading_notification_api.rb +208 -0
- data/lib/activity_notification/apis/notification_api.rb +3 -0
- data/lib/activity_notification/config.rb +10 -0
- data/lib/activity_notification/mailers/helpers.rb +27 -1
- data/lib/activity_notification/models/concerns/swagger/subscription_schema.rb +1 -1
- data/lib/activity_notification/version.rb +1 -1
- data/lib/generators/templates/activity_notification.rb +8 -0
- metadata +45 -481
- data/.codeclimate.yml +0 -33
- data/.coveralls.yml +0 -1
- data/.github/ISSUE_TEMPLATE/bug_report.md +0 -22
- data/.github/ISSUE_TEMPLATE/feature_request.md +0 -17
- data/.github/pull_request_template.md +0 -13
- data/.github/workflows/build.yml +0 -101
- data/.gitignore +0 -74
- data/.rspec +0 -3
- data/.rubocop.yml +0 -1157
- data/.yardopts +0 -6
- data/CHANGELOG.md +0 -439
- data/Gemfile +0 -31
- data/Procfile +0 -2
- data/Rakefile +0 -28
- data/activity_notification.gemspec +0 -44
- data/ai-curated-specs/issues/172/design.md +0 -220
- data/ai-curated-specs/issues/172/tasks.md +0 -326
- data/ai-curated-specs/issues/188/design.md +0 -227
- data/ai-curated-specs/issues/188/requirements.md +0 -78
- data/ai-curated-specs/issues/188/tasks.md +0 -203
- data/ai-curated-specs/issues/188/upstream-contributions.md +0 -592
- data/ai-curated-specs/issues/50/design.md +0 -235
- data/ai-curated-specs/issues/50/requirements.md +0 -49
- data/ai-curated-specs/issues/50/tasks.md +0 -232
- data/bin/_dynamodblocal +0 -4
- data/bin/bundle_update.sh +0 -7
- data/bin/deploy_on_heroku.sh +0 -16
- data/bin/install_dynamodblocal.sh +0 -5
- data/bin/start_dynamodblocal.sh +0 -47
- data/bin/stop_dynamodblocal.sh +0 -34
- data/gemfiles/Gemfile.rails-5.0 +0 -25
- data/gemfiles/Gemfile.rails-5.1 +0 -25
- data/gemfiles/Gemfile.rails-5.2 +0 -24
- data/gemfiles/Gemfile.rails-6.0 +0 -23
- data/gemfiles/Gemfile.rails-6.1 +0 -22
- data/gemfiles/Gemfile.rails-7.0 +0 -25
- data/gemfiles/Gemfile.rails-7.1 +0 -23
- data/gemfiles/Gemfile.rails-7.2 +0 -23
- data/gemfiles/Gemfile.rails-8.0 +0 -24
- data/package.json +0 -8
- data/spec/channels/notification_api_channel_shared_examples.rb +0 -59
- data/spec/channels/notification_api_channel_spec.rb +0 -49
- data/spec/channels/notification_api_with_devise_channel_spec.rb +0 -76
- data/spec/channels/notification_channel_shared_examples.rb +0 -59
- data/spec/channels/notification_channel_spec.rb +0 -48
- data/spec/channels/notification_with_devise_channel_spec.rb +0 -97
- data/spec/concerns/apis/notification_api_spec.rb +0 -1627
- data/spec/concerns/apis/subscription_api_spec.rb +0 -474
- data/spec/concerns/common_spec.rb +0 -213
- data/spec/concerns/models/group_spec.rb +0 -61
- data/spec/concerns/models/notifiable_spec.rb +0 -782
- data/spec/concerns/models/notifier_spec.rb +0 -71
- data/spec/concerns/models/subscriber_spec.rb +0 -800
- data/spec/concerns/models/target_spec.rb +0 -1285
- data/spec/concerns/renderable_spec.rb +0 -129
- data/spec/config_spec.rb +0 -85
- data/spec/controllers/common_controller_spec.rb +0 -25
- data/spec/controllers/controller_spec_utility.rb +0 -100
- data/spec/controllers/dummy_common_controller.rb +0 -5
- data/spec/controllers/notifications_api_controller_shared_examples.rb +0 -619
- data/spec/controllers/notifications_api_controller_spec.rb +0 -19
- data/spec/controllers/notifications_api_with_devise_controller_spec.rb +0 -60
- data/spec/controllers/notifications_controller_shared_examples.rb +0 -743
- data/spec/controllers/notifications_controller_spec.rb +0 -11
- data/spec/controllers/notifications_with_devise_controller_spec.rb +0 -97
- data/spec/controllers/subscriptions_api_controller_shared_examples.rb +0 -750
- data/spec/controllers/subscriptions_api_controller_spec.rb +0 -19
- data/spec/controllers/subscriptions_api_with_devise_controller_spec.rb +0 -60
- data/spec/controllers/subscriptions_controller_shared_examples.rb +0 -946
- data/spec/controllers/subscriptions_controller_spec.rb +0 -11
- data/spec/controllers/subscriptions_with_devise_controller_spec.rb +0 -97
- data/spec/factories/admins.rb +0 -5
- data/spec/factories/articles.rb +0 -5
- data/spec/factories/comments.rb +0 -6
- data/spec/factories/dummy/dummy_group.rb +0 -4
- data/spec/factories/dummy/dummy_notifiable.rb +0 -4
- data/spec/factories/dummy/dummy_notifier.rb +0 -4
- data/spec/factories/dummy/dummy_subscriber.rb +0 -4
- data/spec/factories/dummy/dummy_target.rb +0 -4
- data/spec/factories/notifications.rb +0 -7
- data/spec/factories/subscriptions.rb +0 -8
- data/spec/factories/users.rb +0 -11
- data/spec/generators/controllers_generator_spec.rb +0 -85
- data/spec/generators/install_generator_spec.rb +0 -43
- data/spec/generators/migration/migration_generator_spec.rb +0 -66
- data/spec/generators/models_generator_spec.rb +0 -96
- data/spec/generators/views_generator_spec.rb +0 -195
- data/spec/helpers/polymorphic_helpers_spec.rb +0 -89
- data/spec/helpers/view_helpers_spec.rb +0 -547
- data/spec/jobs/notification_resilience_job_spec.rb +0 -167
- data/spec/jobs/notify_all_job_spec.rb +0 -23
- data/spec/jobs/notify_job_spec.rb +0 -23
- data/spec/jobs/notify_to_job_spec.rb +0 -23
- data/spec/mailers/mailer_spec.rb +0 -214
- data/spec/mailers/notification_resilience_spec.rb +0 -263
- data/spec/models/dummy/dummy_group_spec.rb +0 -10
- data/spec/models/dummy/dummy_notifiable_spec.rb +0 -10
- data/spec/models/dummy/dummy_notifier_spec.rb +0 -10
- data/spec/models/dummy/dummy_subscriber_spec.rb +0 -8
- data/spec/models/dummy/dummy_target_spec.rb +0 -10
- data/spec/models/notification_spec.rb +0 -472
- data/spec/models/subscription_spec.rb +0 -215
- data/spec/optional_targets/action_cable_api_channel_spec.rb +0 -34
- data/spec/optional_targets/action_cable_channel_spec.rb +0 -41
- data/spec/optional_targets/amazon_sns_spec.rb +0 -47
- data/spec/optional_targets/base_spec.rb +0 -45
- data/spec/optional_targets/slack_spec.rb +0 -44
- data/spec/orm/dynamoid_spec.rb +0 -115
- data/spec/rails_app/Rakefile +0 -15
- data/spec/rails_app/app/assets/config/manifest.js +0 -3
- data/spec/rails_app/app/assets/images/.keep +0 -0
- data/spec/rails_app/app/assets/javascripts/application.js +0 -3
- data/spec/rails_app/app/assets/javascripts/cable.js +0 -12
- data/spec/rails_app/app/assets/stylesheets/application.css +0 -15
- data/spec/rails_app/app/assets/stylesheets/reset.css +0 -85
- data/spec/rails_app/app/assets/stylesheets/style.css +0 -244
- data/spec/rails_app/app/controllers/admins_controller.rb +0 -21
- data/spec/rails_app/app/controllers/application_controller.rb +0 -5
- data/spec/rails_app/app/controllers/articles_controller.rb +0 -67
- data/spec/rails_app/app/controllers/comments_controller.rb +0 -36
- data/spec/rails_app/app/controllers/concerns/.keep +0 -0
- data/spec/rails_app/app/controllers/spa_controller.rb +0 -7
- data/spec/rails_app/app/controllers/users/notifications_controller.rb +0 -2
- data/spec/rails_app/app/controllers/users/notifications_with_devise_controller.rb +0 -2
- data/spec/rails_app/app/controllers/users/subscriptions_controller.rb +0 -2
- data/spec/rails_app/app/controllers/users/subscriptions_with_devise_controller.rb +0 -2
- data/spec/rails_app/app/controllers/users_controller.rb +0 -26
- data/spec/rails_app/app/helpers/application_helper.rb +0 -2
- data/spec/rails_app/app/helpers/devise_helper.rb +0 -2
- data/spec/rails_app/app/javascript/App.vue +0 -40
- data/spec/rails_app/app/javascript/components/DeviseTokenAuth.vue +0 -82
- data/spec/rails_app/app/javascript/components/Top.vue +0 -98
- data/spec/rails_app/app/javascript/components/notifications/Index.vue +0 -200
- data/spec/rails_app/app/javascript/components/notifications/Notification.vue +0 -133
- data/spec/rails_app/app/javascript/components/notifications/NotificationContent.vue +0 -122
- data/spec/rails_app/app/javascript/components/subscriptions/Index.vue +0 -279
- data/spec/rails_app/app/javascript/components/subscriptions/NewSubscription.vue +0 -112
- data/spec/rails_app/app/javascript/components/subscriptions/NotificationKey.vue +0 -141
- data/spec/rails_app/app/javascript/components/subscriptions/Subscription.vue +0 -226
- data/spec/rails_app/app/javascript/config/development.js +0 -5
- data/spec/rails_app/app/javascript/config/environment.js +0 -7
- data/spec/rails_app/app/javascript/config/production.js +0 -5
- data/spec/rails_app/app/javascript/config/test.js +0 -5
- data/spec/rails_app/app/javascript/packs/application.js +0 -18
- data/spec/rails_app/app/javascript/packs/spa.js +0 -14
- data/spec/rails_app/app/javascript/router/index.js +0 -73
- data/spec/rails_app/app/javascript/store/index.js +0 -37
- data/spec/rails_app/app/mailers/.keep +0 -0
- data/spec/rails_app/app/mailers/custom_notification_mailer.rb +0 -5
- data/spec/rails_app/app/models/admin.rb +0 -35
- data/spec/rails_app/app/models/article.rb +0 -54
- data/spec/rails_app/app/models/comment.rb +0 -81
- data/spec/rails_app/app/models/dummy/dummy_base.rb +0 -11
- data/spec/rails_app/app/models/dummy/dummy_group.rb +0 -23
- data/spec/rails_app/app/models/dummy/dummy_notifiable.rb +0 -15
- data/spec/rails_app/app/models/dummy/dummy_notifiable_target.rb +0 -27
- data/spec/rails_app/app/models/dummy/dummy_notifier.rb +0 -15
- data/spec/rails_app/app/models/dummy/dummy_subscriber.rb +0 -14
- data/spec/rails_app/app/models/dummy/dummy_target.rb +0 -16
- data/spec/rails_app/app/models/user.rb +0 -73
- data/spec/rails_app/app/views/activity_notification/mailer/dummy_subscribers/test_key.text.erb +0 -1
- data/spec/rails_app/app/views/activity_notification/notifications/default/article/_update.html.erb +0 -146
- data/spec/rails_app/app/views/activity_notification/notifications/default/custom/_path_test.html.erb +0 -1
- data/spec/rails_app/app/views/activity_notification/notifications/default/custom/_test.html.erb +0 -1
- data/spec/rails_app/app/views/activity_notification/notifications/users/_custom_index.html.erb +0 -1
- data/spec/rails_app/app/views/activity_notification/notifications/users/custom/_test.html.erb +0 -1
- data/spec/rails_app/app/views/activity_notification/notifications/users/overridden/custom/_test.html.erb +0 -1
- data/spec/rails_app/app/views/activity_notification/optional_targets/admins/amazon_sns/comment/_default.text.erb +0 -10
- data/spec/rails_app/app/views/articles/_form.html.erb +0 -24
- data/spec/rails_app/app/views/articles/edit.html.erb +0 -8
- data/spec/rails_app/app/views/articles/index.html.erb +0 -113
- data/spec/rails_app/app/views/articles/new.html.erb +0 -7
- data/spec/rails_app/app/views/articles/show.html.erb +0 -49
- data/spec/rails_app/app/views/layouts/_header.html.erb +0 -46
- data/spec/rails_app/app/views/layouts/application.html.erb +0 -15
- data/spec/rails_app/app/views/spa/index.html.erb +0 -2
- data/spec/rails_app/babel.config.js +0 -72
- data/spec/rails_app/bin/bundle +0 -3
- data/spec/rails_app/bin/rails +0 -4
- data/spec/rails_app/bin/rake +0 -4
- data/spec/rails_app/bin/setup +0 -29
- data/spec/rails_app/bin/webpack +0 -18
- data/spec/rails_app/bin/webpack-dev-server +0 -18
- data/spec/rails_app/config/application.rb +0 -54
- data/spec/rails_app/config/boot.rb +0 -5
- data/spec/rails_app/config/cable.yml +0 -8
- data/spec/rails_app/config/database.yml +0 -36
- data/spec/rails_app/config/dynamoid.rb +0 -13
- data/spec/rails_app/config/environment.rb +0 -26
- data/spec/rails_app/config/environments/development.rb +0 -60
- data/spec/rails_app/config/environments/production.rb +0 -85
- data/spec/rails_app/config/environments/test.rb +0 -53
- data/spec/rails_app/config/initializers/activity_notification.rb +0 -104
- data/spec/rails_app/config/initializers/assets.rb +0 -11
- data/spec/rails_app/config/initializers/backtrace_silencers.rb +0 -7
- data/spec/rails_app/config/initializers/cookies_serializer.rb +0 -3
- data/spec/rails_app/config/initializers/copy_it.aws.rb.template +0 -6
- data/spec/rails_app/config/initializers/devise.rb +0 -278
- data/spec/rails_app/config/initializers/devise_token_auth.rb +0 -55
- data/spec/rails_app/config/initializers/filter_parameter_logging.rb +0 -4
- data/spec/rails_app/config/initializers/inflections.rb +0 -16
- data/spec/rails_app/config/initializers/mime_types.rb +0 -4
- data/spec/rails_app/config/initializers/mysql.rb +0 -9
- data/spec/rails_app/config/initializers/session_store.rb +0 -3
- data/spec/rails_app/config/initializers/wrap_parameters.rb +0 -14
- data/spec/rails_app/config/initializers/zeitwerk.rb +0 -10
- data/spec/rails_app/config/locales/activity_notification.en.yml +0 -26
- data/spec/rails_app/config/locales/devise.en.yml +0 -62
- data/spec/rails_app/config/mongoid.yml +0 -13
- data/spec/rails_app/config/routes.rb +0 -50
- data/spec/rails_app/config/secrets.yml +0 -22
- data/spec/rails_app/config/webpack/development.js +0 -5
- data/spec/rails_app/config/webpack/environment.js +0 -7
- data/spec/rails_app/config/webpack/loaders/vue.js +0 -6
- data/spec/rails_app/config/webpack/production.js +0 -5
- data/spec/rails_app/config/webpack/test.js +0 -5
- data/spec/rails_app/config/webpacker.yml +0 -97
- data/spec/rails_app/config.ru +0 -4
- data/spec/rails_app/db/migrate/20160716000000_create_test_tables.rb +0 -42
- data/spec/rails_app/db/migrate/20181209000000_create_activity_notification_tables.rb +0 -33
- data/spec/rails_app/db/migrate/20191201000000_add_tokens_to_users.rb +0 -10
- data/spec/rails_app/db/schema.rb +0 -98
- data/spec/rails_app/db/seeds.rb +0 -95
- data/spec/rails_app/lib/custom_optional_targets/console_output.rb +0 -16
- data/spec/rails_app/lib/custom_optional_targets/raise_error.rb +0 -14
- data/spec/rails_app/lib/custom_optional_targets/wrong_target.rb +0 -13
- data/spec/rails_app/lib/mailer_previews/mailer_preview.rb +0 -29
- data/spec/rails_app/package.json +0 -23
- data/spec/rails_app/postcss.config.js +0 -12
- data/spec/rails_app/public/404.html +0 -67
- data/spec/rails_app/public/422.html +0 -67
- data/spec/rails_app/public/500.html +0 -66
- data/spec/rails_app/public/favicon.ico +0 -0
- data/spec/roles/acts_as_group_spec.rb +0 -30
- data/spec/roles/acts_as_notifiable_spec.rb +0 -432
- data/spec/roles/acts_as_notifier_spec.rb +0 -30
- data/spec/roles/acts_as_target_spec.rb +0 -36
- data/spec/spec_helper.rb +0 -56
- data/spec/version_spec.rb +0 -31
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
module ActivityNotification
|
|
2
|
+
# Defines API for cascading notifications included in Notification model.
|
|
3
|
+
# Cascading notifications enable sequential delivery through different channels
|
|
4
|
+
# based on read status, with configurable time delays between each step.
|
|
5
|
+
module CascadingNotificationApi
|
|
6
|
+
extend ActiveSupport::Concern
|
|
7
|
+
|
|
8
|
+
# Starts a cascading notification chain with the specified configuration.
|
|
9
|
+
# The chain will automatically check the read status before each step and
|
|
10
|
+
# only proceed if the notification remains unread.
|
|
11
|
+
#
|
|
12
|
+
# @example Simple cascade with Slack then email
|
|
13
|
+
# notification.cascade_notify([
|
|
14
|
+
# { delay: 10.minutes, target: :slack },
|
|
15
|
+
# { delay: 10.minutes, target: :email }
|
|
16
|
+
# ])
|
|
17
|
+
#
|
|
18
|
+
# @example Cascade with custom options for each target
|
|
19
|
+
# notification.cascade_notify([
|
|
20
|
+
# { delay: 5.minutes, target: :slack, options: { channel: '#alerts' } },
|
|
21
|
+
# { delay: 10.minutes, target: :amazon_sns, options: { subject: 'Urgent' } },
|
|
22
|
+
# { delay: 15.minutes, target: :email }
|
|
23
|
+
# ])
|
|
24
|
+
#
|
|
25
|
+
# @param [Array<Hash>] cascade_config Array of cascade step configurations
|
|
26
|
+
# @option cascade_config [ActiveSupport::Duration] :delay Required. Time to wait before this step
|
|
27
|
+
# @option cascade_config [Symbol, String] :target Required. Name of the optional target (e.g., :slack, :email)
|
|
28
|
+
# @option cascade_config [Hash] :options Optional. Parameters to pass to the optional target
|
|
29
|
+
# @param [Hash] options Additional options for cascade
|
|
30
|
+
# @option options [Boolean] :validate (true) Whether to validate the cascade configuration
|
|
31
|
+
# @option options [Boolean] :trigger_first_immediately (false) Whether to trigger the first target immediately without delay
|
|
32
|
+
# @return [Boolean] true if cascade was initiated successfully, false otherwise
|
|
33
|
+
# @raise [ArgumentError] if cascade_config is invalid and :validate is true
|
|
34
|
+
def cascade_notify(cascade_config, options = {})
|
|
35
|
+
validate = options.fetch(:validate, true)
|
|
36
|
+
trigger_first_immediately = options.fetch(:trigger_first_immediately, false)
|
|
37
|
+
|
|
38
|
+
# Validate configuration if requested
|
|
39
|
+
if validate
|
|
40
|
+
validation_result = validate_cascade_config(cascade_config)
|
|
41
|
+
unless validation_result[:valid]
|
|
42
|
+
raise ArgumentError, "Invalid cascade configuration: #{validation_result[:errors].join(', ')}"
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Return false if cascade config is empty
|
|
47
|
+
return false if cascade_config.blank?
|
|
48
|
+
|
|
49
|
+
# Return false if notification is already opened
|
|
50
|
+
return false if opened?
|
|
51
|
+
|
|
52
|
+
if defined?(ActiveJob) && defined?(ActivityNotification::CascadingNotificationJob) &&
|
|
53
|
+
ActivityNotification::CascadingNotificationJob.respond_to?(:perform_later)
|
|
54
|
+
if trigger_first_immediately && cascade_config.any?
|
|
55
|
+
# Trigger first target immediately
|
|
56
|
+
first_step = cascade_config.first
|
|
57
|
+
target_name = first_step[:target] || first_step['target']
|
|
58
|
+
target_options = first_step[:options] || first_step['options'] || {}
|
|
59
|
+
|
|
60
|
+
# Perform the first step synchronously
|
|
61
|
+
perform_cascade_step(target_name, target_options)
|
|
62
|
+
|
|
63
|
+
# Schedule remaining steps if any
|
|
64
|
+
if cascade_config.length > 1
|
|
65
|
+
remaining_config = cascade_config[1..-1]
|
|
66
|
+
first_delay = remaining_config.first[:delay] || remaining_config.first['delay']
|
|
67
|
+
|
|
68
|
+
if first_delay.present?
|
|
69
|
+
ActivityNotification::CascadingNotificationJob
|
|
70
|
+
.set(wait: first_delay)
|
|
71
|
+
.perform_later(id, remaining_config, 0)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
else
|
|
75
|
+
# Schedule first step with its configured delay
|
|
76
|
+
first_step = cascade_config.first
|
|
77
|
+
first_delay = first_step[:delay] || first_step['delay']
|
|
78
|
+
|
|
79
|
+
if first_delay.present?
|
|
80
|
+
ActivityNotification::CascadingNotificationJob
|
|
81
|
+
.set(wait: first_delay)
|
|
82
|
+
.perform_later(id, cascade_config, 0)
|
|
83
|
+
else
|
|
84
|
+
# If no delay specified for first step, trigger immediately
|
|
85
|
+
ActivityNotification::CascadingNotificationJob
|
|
86
|
+
.perform_later(id, cascade_config, 0)
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
true
|
|
91
|
+
else
|
|
92
|
+
Rails.logger.error("ActiveJob or CascadingNotificationJob not available for cascading notifications")
|
|
93
|
+
false
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Validates a cascade configuration array
|
|
98
|
+
#
|
|
99
|
+
# @param [Array<Hash>] cascade_config The configuration to validate
|
|
100
|
+
# @return [Hash] Hash with :valid (Boolean) and :errors (Array<String>) keys
|
|
101
|
+
def validate_cascade_config(cascade_config)
|
|
102
|
+
errors = []
|
|
103
|
+
|
|
104
|
+
if cascade_config.nil?
|
|
105
|
+
errors << "cascade_config cannot be nil"
|
|
106
|
+
return { valid: false, errors: errors }
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
unless cascade_config.is_a?(Array)
|
|
110
|
+
errors << "cascade_config must be an Array"
|
|
111
|
+
return { valid: false, errors: errors }
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
if cascade_config.empty?
|
|
115
|
+
errors << "cascade_config cannot be empty"
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
cascade_config.each_with_index do |step, index|
|
|
119
|
+
unless step.is_a?(Hash)
|
|
120
|
+
errors << "Step #{index} must be a Hash"
|
|
121
|
+
next
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# Check for required target parameter
|
|
125
|
+
target = step[:target] || step['target']
|
|
126
|
+
if target.nil?
|
|
127
|
+
errors << "Step #{index} missing required :target parameter"
|
|
128
|
+
elsif !target.is_a?(Symbol) && !target.is_a?(String)
|
|
129
|
+
errors << "Step #{index} :target must be a Symbol or String"
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Check for delay parameter (only required for steps after the first if not using trigger_first_immediately)
|
|
133
|
+
delay = step[:delay] || step['delay']
|
|
134
|
+
if delay.nil?
|
|
135
|
+
errors << "Step #{index} missing :delay parameter"
|
|
136
|
+
elsif !delay.respond_to?(:from_now) && !delay.is_a?(Numeric)
|
|
137
|
+
errors << "Step #{index} :delay must be an ActiveSupport::Duration or Numeric (seconds)"
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# Check options if present
|
|
141
|
+
options = step[:options] || step['options']
|
|
142
|
+
if options.present? && !options.is_a?(Hash)
|
|
143
|
+
errors << "Step #{index} :options must be a Hash"
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
{ valid: errors.empty?, errors: errors }
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# Checks if a cascading notification is currently in progress for this notification
|
|
151
|
+
# This is a helper method that checks if there are scheduled jobs for this notification
|
|
152
|
+
#
|
|
153
|
+
# @return [Boolean] true if cascade jobs are scheduled (this is a best-effort check)
|
|
154
|
+
def cascade_in_progress?
|
|
155
|
+
# This is a best-effort check that returns false by default
|
|
156
|
+
# In production, you might want to track this state differently
|
|
157
|
+
# (e.g., in Redis, database flag, or by querying the job queue)
|
|
158
|
+
false
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
private
|
|
162
|
+
|
|
163
|
+
# Performs a single cascade step immediately (synchronously)
|
|
164
|
+
# @api private
|
|
165
|
+
# @param [Symbol, String] target_name Name of the optional target
|
|
166
|
+
# @param [Hash] options Options to pass to the optional target
|
|
167
|
+
# @return [Hash] Result of the operation
|
|
168
|
+
def perform_cascade_step(target_name, options = {})
|
|
169
|
+
target_name_sym = target_name.to_sym
|
|
170
|
+
|
|
171
|
+
# Get all configured optional targets for this notification
|
|
172
|
+
optional_targets = notifiable.optional_targets(
|
|
173
|
+
target.to_resources_name,
|
|
174
|
+
key
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
# Find the matching optional target
|
|
178
|
+
optional_target = optional_targets.find do |ot|
|
|
179
|
+
ot.to_optional_target_name == target_name_sym
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
if optional_target.nil?
|
|
183
|
+
Rails.logger.warn("Optional target '#{target_name}' not found for notification #{id}")
|
|
184
|
+
return { target_name_sym => :not_configured }
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
# Check subscription status
|
|
188
|
+
unless optional_target_subscribed?(target_name_sym)
|
|
189
|
+
Rails.logger.info("Target not subscribed to optional target '#{target_name}' for notification #{id}")
|
|
190
|
+
return { target_name_sym => :not_subscribed }
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
# Trigger the optional target
|
|
194
|
+
begin
|
|
195
|
+
optional_target.notify(self, options)
|
|
196
|
+
Rails.logger.info("Successfully triggered optional target '#{target_name}' for notification #{id}")
|
|
197
|
+
{ target_name_sym => :success }
|
|
198
|
+
rescue => e
|
|
199
|
+
Rails.logger.error("Failed to trigger optional target '#{target_name}' for notification #{id}: #{e.message}")
|
|
200
|
+
if ActivityNotification.config.rescue_optional_target_errors
|
|
201
|
+
{ target_name_sym => e }
|
|
202
|
+
else
|
|
203
|
+
raise e
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
end
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
require 'activity_notification/apis/cascading_notification_api'
|
|
2
|
+
|
|
1
3
|
module ActivityNotification
|
|
2
4
|
# Defines API for notification included in Notification model.
|
|
3
5
|
module NotificationApi
|
|
4
6
|
extend ActiveSupport::Concern
|
|
7
|
+
include CascadingNotificationApi
|
|
5
8
|
|
|
6
9
|
included do
|
|
7
10
|
# Defines store_notification as private clas method
|
|
@@ -82,6 +82,15 @@ module ActivityNotification
|
|
|
82
82
|
# @return [String] Email address as sender of notification email.
|
|
83
83
|
attr_accessor :mailer_sender
|
|
84
84
|
|
|
85
|
+
# @overload mailer_cc
|
|
86
|
+
# Returns carbon copy (CC) email address(es) for notification email
|
|
87
|
+
# @return [String, Array<String>, Proc] CC email address(es) for notification email.
|
|
88
|
+
# @overload mailer_cc=(value)
|
|
89
|
+
# Sets carbon copy (CC) email address(es) for notification email
|
|
90
|
+
# @param [String, Array<String>, Proc] mailer_cc The new mailer_cc
|
|
91
|
+
# @return [String, Array<String>, Proc] CC email address(es) for notification email.
|
|
92
|
+
attr_accessor :mailer_cc
|
|
93
|
+
|
|
85
94
|
# @overload mailer
|
|
86
95
|
# Returns mailer class for email notification
|
|
87
96
|
# @return [String] Mailer class for email notification.
|
|
@@ -236,6 +245,7 @@ module ActivityNotification
|
|
|
236
245
|
@subscribe_to_email_as_default = nil
|
|
237
246
|
@subscribe_to_optional_targets_as_default = nil
|
|
238
247
|
@mailer_sender = nil
|
|
248
|
+
@mailer_cc = nil
|
|
239
249
|
@mailer = 'ActivityNotification::Mailer'
|
|
240
250
|
@parent_mailer = 'ActionMailer::Base'
|
|
241
251
|
@parent_job = 'ActiveJob::Base'
|
|
@@ -74,6 +74,7 @@ module ActivityNotification
|
|
|
74
74
|
subject: :subject_for,
|
|
75
75
|
from: :mailer_from,
|
|
76
76
|
reply_to: :mailer_reply_to,
|
|
77
|
+
cc: :mailer_cc,
|
|
77
78
|
message_id: nil
|
|
78
79
|
}.each do |header_name, default_method|
|
|
79
80
|
overridding_method_name = "overriding_notification_email_#{header_name.to_s}"
|
|
@@ -81,7 +82,12 @@ module ActivityNotification
|
|
|
81
82
|
@notification.notifiable.send(overridding_method_name, @target, key).present?
|
|
82
83
|
@notification.notifiable.send(overridding_method_name, @target, key)
|
|
83
84
|
elsif default_method
|
|
84
|
-
|
|
85
|
+
# Special handling for methods that take target instead of key
|
|
86
|
+
if [:mailer_cc].include?(default_method)
|
|
87
|
+
send(default_method, @target)
|
|
88
|
+
else
|
|
89
|
+
send(default_method, key)
|
|
90
|
+
end
|
|
85
91
|
else
|
|
86
92
|
nil
|
|
87
93
|
end
|
|
@@ -99,6 +105,26 @@ module ActivityNotification
|
|
|
99
105
|
target.mailer_to
|
|
100
106
|
end
|
|
101
107
|
|
|
108
|
+
# Returns carbon copy (CC) email address(es).
|
|
109
|
+
#
|
|
110
|
+
# @param [Object] target Target instance to notify
|
|
111
|
+
# @return [String, Array<String>, nil] CC email address(es) or nil
|
|
112
|
+
def mailer_cc(target)
|
|
113
|
+
if target.respond_to?(:mailer_cc)
|
|
114
|
+
target.mailer_cc
|
|
115
|
+
elsif ActivityNotification.config.mailer_cc.present?
|
|
116
|
+
if ActivityNotification.config.mailer_cc.is_a?(Proc)
|
|
117
|
+
# Get the notification key from current context
|
|
118
|
+
key = @notification ? @notification.key : nil
|
|
119
|
+
ActivityNotification.config.mailer_cc.call(key)
|
|
120
|
+
else
|
|
121
|
+
ActivityNotification.config.mailer_cc
|
|
122
|
+
end
|
|
123
|
+
else
|
|
124
|
+
nil
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
102
128
|
# Returns sender email address as 'reply_to'.
|
|
103
129
|
#
|
|
104
130
|
# @param [String] key Key of the notification or batch notification email
|
|
@@ -45,6 +45,14 @@ ActivityNotification.configure do |config|
|
|
|
45
45
|
# note that it will be overwritten if you use your own mailer class with default "from" parameter.
|
|
46
46
|
config.mailer_sender = 'please-change-me-at-config-initializers-activity_notification@example.com'
|
|
47
47
|
|
|
48
|
+
# Configure the carbon copy (CC) email address(es) for notification emails.
|
|
49
|
+
# You can set a single email address, an array of email addresses, or a Proc that returns either.
|
|
50
|
+
# Note that this can be overridden per target by defining a mailer_cc method in the target model,
|
|
51
|
+
# or per notification by defining overriding_notification_email_cc in the notifiable model.
|
|
52
|
+
# config.mailer_cc = 'admin@example.com'
|
|
53
|
+
# config.mailer_cc = ['admin@example.com', 'support@example.com']
|
|
54
|
+
# config.mailer_cc = ->(key){ key.include?('urgent') ? 'urgent@example.com' : nil }
|
|
55
|
+
|
|
48
56
|
# Configure the class responsible to send e-mails.
|
|
49
57
|
# config.mailer = "ActivityNotification::Mailer"
|
|
50
58
|
|