noticent 0.0.1.pre.pre → 0.0.5
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/.gitignore +2 -0
- data/.ruby-version +1 -2
- data/Gemfile.lock +154 -74
- data/LICENSE +201 -0
- data/README.md +81 -14
- data/config.ru +9 -0
- data/lib/noticent.rb +2 -1
- data/lib/noticent/active_record_opt_in_provider.rb +10 -1
- data/lib/noticent/channel.rb +24 -31
- data/lib/noticent/config.rb +77 -18
- data/lib/noticent/definitions/alert.rb +80 -6
- data/lib/noticent/definitions/channel.rb +7 -2
- data/lib/noticent/definitions/scope.rb +10 -6
- data/lib/noticent/dispatcher.rb +16 -10
- data/lib/noticent/version.rb +1 -1
- data/lib/noticent/view.rb +16 -14
- data/noticent.gemspec +27 -21
- data/testing/channels/email.rb +1 -0
- data/testing/channels/exclusive.rb +9 -0
- data/testing/channels/simple.rb +9 -0
- data/testing/payloads/comment_payload.rb +10 -0
- data/testing/payloads/post_payload.rb +44 -0
- data/testing/views/email/some_event.html.erb +3 -0
- data/testing/views/email/some_event.txt.erb +2 -0
- data/testing/views/layouts/layout.html.erb +1 -1
- data/testing/views/simple/default.html.erb +1 -0
- metadata +85 -24
@@ -8,12 +8,15 @@ module Noticent
|
|
8
8
|
attr_reader :notifiers
|
9
9
|
attr_reader :config
|
10
10
|
attr_reader :products
|
11
|
+
attr_reader :constructor_name
|
11
12
|
|
12
|
-
def initialize(config, name:, scope:)
|
13
|
+
def initialize(config, name:, scope:, constructor_name:)
|
13
14
|
@config = config
|
14
15
|
@name = name
|
15
16
|
@scope = scope
|
17
|
+
@constructor_name = constructor_name
|
16
18
|
@products = Noticent::Definitions::ProductGroup.new(@config)
|
19
|
+
@defaults = { _any_: Noticent::Definitions::Alert::DefaultValue.new(self, :_any_, config.default_value) }
|
17
20
|
end
|
18
21
|
|
19
22
|
def notify(recipient, template: '')
|
@@ -27,21 +30,54 @@ module Noticent
|
|
27
30
|
alert_notifier
|
28
31
|
end
|
29
32
|
|
33
|
+
def default_for(channel)
|
34
|
+
raise ArgumentError, "no channel named '#{channel}' found" if @config.channels[channel].nil?
|
35
|
+
|
36
|
+
@defaults[channel].nil? ? @defaults[:_any_].value : @defaults[channel].value
|
37
|
+
end
|
38
|
+
|
39
|
+
def default_value
|
40
|
+
@defaults[:_any_].value
|
41
|
+
end
|
42
|
+
|
43
|
+
def default(value, &block)
|
44
|
+
defaults = @defaults
|
45
|
+
|
46
|
+
if block_given?
|
47
|
+
default = Noticent::Definitions::Alert::DefaultValue.new(self, :_any_, value)
|
48
|
+
default.instance_eval(&block)
|
49
|
+
|
50
|
+
defaults[default.channel] = default
|
51
|
+
else
|
52
|
+
defaults[:_any_].value = value
|
53
|
+
end
|
54
|
+
|
55
|
+
@defaults = defaults
|
56
|
+
|
57
|
+
default
|
58
|
+
end
|
59
|
+
|
30
60
|
def applies
|
31
61
|
@products
|
32
62
|
end
|
33
63
|
|
34
64
|
def validate!
|
35
65
|
channels = @config.alert_channels(@name)
|
66
|
+
raise BadConfiguration, "no notifiers are assigned to alert '#{@name}'" if @notifiers.nil? || @notifiers.empty?
|
67
|
+
|
36
68
|
channels.each do |channel|
|
37
69
|
raise BadConfiguration, "channel #{channel.name} (#{channel.klass}) has no method called #{@name}" unless channel.klass.method_defined? @name
|
38
70
|
end
|
71
|
+
|
72
|
+
# if a payload class is available, we can make sure it has a constructor with the name of the event
|
73
|
+
raise Noticent::BadConfiguration, "payload #{@scope.payload_class} doesn't have a class method called #{name}" if @scope.check_constructor && !@scope.payload_class.respond_to?(@constructor_name)
|
39
74
|
end
|
40
75
|
|
41
76
|
# holds a list of recipient + channel
|
42
77
|
class Notifier
|
43
78
|
attr_reader :recipient
|
44
|
-
attr_reader :channel_group
|
79
|
+
attr_reader :channel_group # group to be notified
|
80
|
+
attr_reader :channel # channel to be notified
|
45
81
|
attr_reader :template
|
46
82
|
|
47
83
|
def initialize(alert, recipient, template: '')
|
@@ -50,13 +86,51 @@ module Noticent
|
|
50
86
|
@config = alert.config
|
51
87
|
@template = template
|
52
88
|
@channel_group = :default
|
89
|
+
@channel = nil
|
90
|
+
end
|
91
|
+
|
92
|
+
def on(channel_group_or_name)
|
93
|
+
# is it a group or a channel name?
|
94
|
+
if @config.channel_groups.include? channel_group_or_name
|
95
|
+
# it's a group
|
96
|
+
@channel_group = channel_group_or_name
|
97
|
+
@channel = nil
|
98
|
+
elsif !@config.channels[channel_group_or_name].nil?
|
99
|
+
@channel_group = :_none_
|
100
|
+
@channel = @config.channels[channel_group_or_name]
|
101
|
+
else
|
102
|
+
# not a group and not a channel
|
103
|
+
raise ArgumentError, "no channel or channel group found named '#{channel_group_or_name}'"
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
# returns an array of all channels this notifier should send to
|
108
|
+
def applicable_channels
|
109
|
+
if @channel_group == :_none_
|
110
|
+
# it's a single channel
|
111
|
+
[@channel]
|
112
|
+
else
|
113
|
+
@config.channels_by_group(@channel_group)
|
114
|
+
end
|
53
115
|
end
|
116
|
+
end
|
117
|
+
|
118
|
+
class DefaultValue
|
119
|
+
attr_reader :channel
|
120
|
+
attr_accessor :value
|
121
|
+
|
122
|
+
def initialize(alert, channel, value)
|
123
|
+
@alert = alert
|
124
|
+
@channel = channel
|
125
|
+
@value = value
|
126
|
+
end
|
127
|
+
|
128
|
+
def on(channel)
|
129
|
+
raise BadConfiguration, "no channel named '#{channel}'" if @alert.config.channels[channel].nil?
|
54
130
|
|
55
|
-
|
56
|
-
# validate the group name
|
57
|
-
raise ArgumentError, "no channel group found named '#{channel_group}'" if @config.channels_by_group(channel_group).empty?
|
131
|
+
@channel = channel
|
58
132
|
|
59
|
-
|
133
|
+
self
|
60
134
|
end
|
61
135
|
end
|
62
136
|
end
|
@@ -9,11 +9,17 @@ module Noticent
|
|
9
9
|
attr_reader :options
|
10
10
|
|
11
11
|
def initialize(config, name, group: :default, klass: nil)
|
12
|
+
raise BadConfiguration, 'name should be a symbol' unless name.is_a? Symbol
|
13
|
+
raise BadConfiguration, '\'_any_\' is a reserved channel name' if name == :_any_
|
14
|
+
raise BadConfiguration, '\'_none_\' is a reserved channel name' if name == :_none_
|
15
|
+
|
12
16
|
@name = name
|
13
17
|
@group = group
|
14
18
|
@config = config
|
15
19
|
|
16
|
-
|
20
|
+
sub_module = @config.use_sub_modules ? '::Channels::' : '::'
|
21
|
+
suggested_class_name = @config.base_module_name + sub_module + name.to_s.camelize
|
22
|
+
|
17
23
|
@klass = klass.nil? ? suggested_class_name.camelize.constantize : klass
|
18
24
|
rescue NameError
|
19
25
|
raise Noticent::BadConfiguration, "no class found for #{suggested_class_name}"
|
@@ -37,7 +43,6 @@ module Noticent
|
|
37
43
|
rescue ArgumentError
|
38
44
|
raise Noticent::BadConfiguration, "channel #{@klass} initializer arguments are mismatching."
|
39
45
|
end
|
40
|
-
|
41
46
|
end
|
42
47
|
end
|
43
48
|
end
|
@@ -5,22 +5,26 @@ module Noticent
|
|
5
5
|
class Scope
|
6
6
|
attr_reader :name
|
7
7
|
attr_reader :payload_class
|
8
|
+
attr_reader :check_constructor
|
8
9
|
|
9
|
-
def initialize(config, name, payload_class: nil)
|
10
|
+
def initialize(config, name, payload_class: nil, check_constructor: true)
|
10
11
|
@config = config
|
11
12
|
@name = name
|
12
|
-
|
13
|
-
|
13
|
+
@check_constructor = check_constructor
|
14
|
+
|
15
|
+
sub_module = @config.use_sub_modules ? '::Payloads::' : '::'
|
16
|
+
suggested_name = config.base_module_name + sub_module + "#{name.capitalize}Payload"
|
17
|
+
@payload_class = payload_class.nil? ? suggested_name.constantize : payload_class
|
14
18
|
rescue NameError
|
15
|
-
raise BadConfiguration, "scope #{
|
19
|
+
raise BadConfiguration, "scope #{suggested_name} class not found"
|
16
20
|
end
|
17
21
|
|
18
|
-
def alert(name, &block)
|
22
|
+
def alert(name, constructor_name: nil, &block)
|
19
23
|
alerts = @config.instance_variable_get(:@alerts) || {}
|
20
24
|
|
21
25
|
raise BadConfiguration, "alert '#{name}' already defined" if alerts.include? name
|
22
26
|
|
23
|
-
alert = Noticent::Definitions::Alert.new(@config, name: name, scope: self)
|
27
|
+
alert = Noticent::Definitions::Alert.new(@config, name: name, scope: self, constructor_name: constructor_name.nil? ? name : constructor_name)
|
24
28
|
@config.hooks&.run(:pre_alert_registration, alert)
|
25
29
|
alert.instance_eval(&block) if block_given?
|
26
30
|
@config.hooks&.run(:post_alert_registration, alert)
|
data/lib/noticent/dispatcher.rb
CHANGED
@@ -2,11 +2,11 @@
|
|
2
2
|
|
3
3
|
module Noticent
|
4
4
|
class Dispatcher
|
5
|
-
def initialize(config, alert_name, payload,
|
5
|
+
def initialize(config, alert_name, payload, configuration = {})
|
6
6
|
@config = config
|
7
7
|
@alert_name = alert_name
|
8
8
|
@payload = payload
|
9
|
-
@
|
9
|
+
@configuration = configuration
|
10
10
|
|
11
11
|
validate!
|
12
12
|
|
@@ -34,8 +34,8 @@ module Noticent
|
|
34
34
|
|
35
35
|
# only returns recipients that have opted-in for this channel
|
36
36
|
def filter_recipients(recipients, channel)
|
37
|
-
raise ArgumentError,
|
38
|
-
raise ArgumentError,
|
37
|
+
raise ArgumentError, "channel should be a string or symbol" unless channel.is_a?(String) || channel.is_a?(Symbol)
|
38
|
+
raise ArgumentError, "recipients is nil" if recipients.nil?
|
39
39
|
|
40
40
|
recipients.select { |recipient| @config.opt_in_provider.opted_in?(recipient_id: recipient.id, scope: scope.name, entity_id: @entity_id, alert_name: alert.name, channel_name: channel) }
|
41
41
|
end
|
@@ -43,9 +43,15 @@ module Noticent
|
|
43
43
|
def dispatch
|
44
44
|
notifiers.values.each do |notifier|
|
45
45
|
recs = recipients(notifier.recipient)
|
46
|
-
|
46
|
+
notifier.applicable_channels.each do |channel|
|
47
47
|
to_send = filter_recipients(recs, channel.name)
|
48
|
-
|
48
|
+
|
49
|
+
if to_send.count == 0 && @config.skip_alert_with_no_subscribers
|
50
|
+
Noticent.configuration.logger.info "Skipping notification of alert #{alert.name} on channel #{channel.name} as there are no subscribers"
|
51
|
+
next
|
52
|
+
end
|
53
|
+
|
54
|
+
channel_instance = channel.instance(@config, to_send, @payload, @configuration)
|
49
55
|
begin
|
50
56
|
raise Noticent::BadConfiguration, "channel #{channel.name} (#{channel.klass}) doesn't have a method called #{alert.name}" unless channel_instance.respond_to? alert.name
|
51
57
|
|
@@ -54,7 +60,7 @@ module Noticent
|
|
54
60
|
# log and move on
|
55
61
|
raise if @config.halt_on_error
|
56
62
|
|
57
|
-
Noticent.logger.error e
|
63
|
+
Noticent.configuration.logger.error e
|
58
64
|
end
|
59
65
|
end
|
60
66
|
end
|
@@ -63,12 +69,12 @@ module Noticent
|
|
63
69
|
private
|
64
70
|
|
65
71
|
def validate!
|
66
|
-
raise Noticent::BadConfiguration,
|
72
|
+
raise Noticent::BadConfiguration, "no base_dir defined" if @config.base_dir.nil?
|
67
73
|
raise Noticent::MissingConfiguration if @config.nil?
|
68
74
|
raise Noticent::BadConfiguration if @config.alerts.nil?
|
69
75
|
raise Noticent::InvalidAlert, "no alert #{@alert_name} found" if @config.alerts[@alert_name].nil?
|
70
|
-
raise ::ArgumentError,
|
71
|
-
raise ::ArgumentError,
|
76
|
+
raise ::ArgumentError, "payload is nil" if @payload.nil?
|
77
|
+
raise ::ArgumentError, "alert is not a symbol" unless @alert_name.is_a?(Symbol)
|
72
78
|
raise Noticent::BadConfiguration, "payload (#{@payload.class}) doesn't belong to this scope (#{scope.name}) as it requires #{scope.payload_class}" unless !scope.payload_class.nil? && @payload.is_a?(scope.payload_class)
|
73
79
|
raise Noticent::BadConfiguration, "payload doesn't have a #{scope.name}_id method" unless @payload.respond_to?("#{scope.name}_id")
|
74
80
|
end
|
data/lib/noticent/version.rb
CHANGED
data/lib/noticent/view.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require "yaml"
|
4
4
|
|
5
5
|
module Noticent
|
6
6
|
class View
|
@@ -16,38 +16,40 @@ module Noticent
|
|
16
16
|
attr_reader :raw_data # frontmatter in their raw (pre render) format
|
17
17
|
attr_reader :rendered_data # frontmatter rendered in string format
|
18
18
|
|
19
|
-
def initialize(filename, template_filename:
|
19
|
+
def initialize(filename, template_filename: "", channel:)
|
20
20
|
raise ViewNotFound, "view #{filename} not found" unless File.exist?(filename)
|
21
|
-
raise ViewNotFound, "template #{template_filename} not found" if template_filename !=
|
22
|
-
raise ArgumentError,
|
21
|
+
raise ViewNotFound, "template #{template_filename} not found" if template_filename != "" && !File.exist?(template_filename)
|
22
|
+
raise ArgumentError, "channel is nil" if channel.nil?
|
23
23
|
|
24
24
|
@filename = filename
|
25
25
|
@view_content = File.read(filename)
|
26
|
-
@template_content = template_filename !=
|
27
|
-
@template_filename = template_filename !=
|
26
|
+
@template_content = template_filename != "" ? File.read(template_filename) : "<%= @content %>"
|
27
|
+
@template_filename = template_filename != "" ? template_filename : ""
|
28
28
|
@channel = channel
|
29
29
|
end
|
30
30
|
|
31
|
-
def process
|
31
|
+
def process(context)
|
32
32
|
parse
|
33
|
-
|
34
|
-
render_data
|
33
|
+
render_data(context)
|
35
34
|
read_data
|
35
|
+
# TODO this is nasty. we need to refactor to have an independent render context which somehow merges the binding with the channel.
|
36
|
+
@channel.data = @data
|
37
|
+
render_content(context)
|
36
38
|
end
|
37
39
|
|
38
40
|
private
|
39
41
|
|
40
|
-
def render_content
|
41
|
-
@content = @channel.render_within_context(@template_content, @
|
42
|
+
def render_content(context)
|
43
|
+
@content = @channel.render_within_context(template: @template_content, content: @raw_content, context: context)
|
42
44
|
end
|
43
45
|
|
44
|
-
def render_data
|
46
|
+
def render_data(context)
|
45
47
|
if @raw_data.nil?
|
46
48
|
@rendered_data = nil
|
47
49
|
return
|
48
50
|
end
|
49
51
|
|
50
|
-
@rendered_data = @channel.render_within_context(nil, @raw_data)
|
52
|
+
@rendered_data = @channel.render_within_context(template: nil, content: @raw_data, context: context)
|
51
53
|
end
|
52
54
|
|
53
55
|
def parse
|
@@ -70,7 +72,7 @@ module Noticent
|
|
70
72
|
if @raw_data.nil?
|
71
73
|
@data = nil
|
72
74
|
else
|
73
|
-
raise ArgumentError,
|
75
|
+
raise ArgumentError, "read_data was called before rendering" if @rendered_data.nil?
|
74
76
|
|
75
77
|
data = ::YAML.safe_load(@rendered_data)
|
76
78
|
@data = data.deep_symbolize_keys
|
data/noticent.gemspec
CHANGED
@@ -1,36 +1,42 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
lib = File.expand_path(
|
3
|
+
lib = File.expand_path("lib", __dir__)
|
4
4
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
|
-
require
|
5
|
+
require "noticent/version"
|
6
6
|
|
7
7
|
Gem::Specification.new do |spec|
|
8
|
-
spec.name
|
9
|
-
spec.version
|
10
|
-
spec.authors
|
11
|
-
spec.email
|
8
|
+
spec.name = "noticent"
|
9
|
+
spec.version = Noticent::VERSION
|
10
|
+
spec.authors = ["Khash Sajadi"]
|
11
|
+
spec.email = ["khash@cloud66.com"]
|
12
|
+
spec.license = "Apache-2.0"
|
13
|
+
spec.homepage = "https://github.com/cloud66-oss/noticent"
|
12
14
|
|
13
|
-
spec.summary
|
15
|
+
spec.summary = "Act as Notified is a flexible framework to add notifications to a Rails application"
|
14
16
|
|
15
17
|
# Specify which files should be added to the gem when it is released.
|
16
18
|
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
17
19
|
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
18
20
|
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
19
21
|
end
|
20
|
-
spec.bindir
|
21
|
-
spec.executables
|
22
|
-
spec.require_paths = [
|
22
|
+
spec.bindir = "exe"
|
23
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
24
|
+
spec.require_paths = ["lib"]
|
23
25
|
|
24
|
-
spec.add_dependency
|
25
|
-
spec.add_dependency
|
26
|
+
spec.add_dependency "activerecord", "~> 6.0"
|
27
|
+
spec.add_dependency "activesupport", "~> 6.0"
|
28
|
+
spec.add_dependency "actionpack", "~> 6.0"
|
26
29
|
|
27
|
-
spec.add_development_dependency
|
28
|
-
spec.add_development_dependency
|
29
|
-
spec.add_development_dependency
|
30
|
-
spec.add_development_dependency
|
31
|
-
spec.add_development_dependency
|
32
|
-
spec.add_development_dependency
|
33
|
-
spec.add_development_dependency
|
34
|
-
spec.add_development_dependency
|
35
|
-
spec.add_development_dependency
|
30
|
+
spec.add_development_dependency "combustion", "~> 1.3"
|
31
|
+
spec.add_development_dependency "bundler", "~> 2.0"
|
32
|
+
spec.add_development_dependency "factory_bot", "~> 6.1"
|
33
|
+
spec.add_development_dependency "generator_spec", "~> 0.9"
|
34
|
+
spec.add_development_dependency "rails", "~> 6.0"
|
35
|
+
spec.add_development_dependency "rake", "~> 13.0"
|
36
|
+
spec.add_development_dependency "rspec", "~> 3.10"
|
37
|
+
spec.add_development_dependency "rubocop", "~> 1.4"
|
38
|
+
spec.add_development_dependency "rubocop-performance", "~> 1.9"
|
39
|
+
spec.add_development_dependency "rufo", "~> 0.12"
|
40
|
+
spec.add_development_dependency "sqlite3", "~> 1.4"
|
41
|
+
spec.add_development_dependency "byebug", "~> 11.1"
|
36
42
|
end
|
data/testing/channels/email.rb
CHANGED
@@ -3,6 +3,16 @@ module Noticent
|
|
3
3
|
# this is a example payload for comment as scope
|
4
4
|
class CommentPayload < Noticent::Testing::Payload
|
5
5
|
attr_accessor :comment_id
|
6
|
+
attr_reader :users
|
7
|
+
|
8
|
+
def self.three
|
9
|
+
# nop
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.new_signup
|
13
|
+
# nop
|
14
|
+
end
|
15
|
+
|
6
16
|
end
|
7
17
|
end
|
8
18
|
end
|
@@ -16,6 +16,50 @@ module Noticent
|
|
16
16
|
[]
|
17
17
|
end
|
18
18
|
|
19
|
+
def self.some_event(some_value)
|
20
|
+
# nop
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.foo(options)
|
24
|
+
# nop
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.boo
|
28
|
+
# nop
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.one
|
32
|
+
# nop
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.only_here
|
36
|
+
# nop
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.two
|
40
|
+
# nop
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.tfa_enabled
|
44
|
+
# nop
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.sign_up
|
48
|
+
# nop
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.new_signup
|
52
|
+
# nop
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.fuzz
|
56
|
+
# nop
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.buzz
|
60
|
+
# nop
|
61
|
+
end
|
62
|
+
|
19
63
|
end
|
20
64
|
end
|
21
65
|
end
|