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.
@@ -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
- def on(channel_group)
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
- @channel_group = channel_group
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
- suggested_class_name = @config.base_module_name + '::' + name.to_s.camelize
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
- suggeste_name = config.base_module_name + "::#{name.capitalize}Payload"
13
- @payload_class = payload_class.nil? ? suggeste_name.constantize : payload_class
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 #{suggeste_name} class not found"
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)
@@ -2,11 +2,11 @@
2
2
 
3
3
  module Noticent
4
4
  class Dispatcher
5
- def initialize(config, alert_name, payload, context = {})
5
+ def initialize(config, alert_name, payload, configuration = {})
6
6
  @config = config
7
7
  @alert_name = alert_name
8
8
  @payload = payload
9
- @context = context
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, '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?
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
- @config.channels_by_group(notifier.channel_group).each do |channel|
46
+ notifier.applicable_channels.each do |channel|
47
47
  to_send = filter_recipients(recs, channel.name)
48
- channel_instance = channel.instance(@config, to_send, @payload, @context)
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, 'no base_dir defined' if @config.base_dir.nil?
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, 'payload is nil' if @payload.nil?
71
- raise ::ArgumentError, 'alert is not a symbol' unless @alert_name.is_a?(Symbol)
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Noticent
4
- VERSION ||= '0.0.1-pre'
4
+ VERSION ||= "0.0.5"
5
5
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'yaml'
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: '', channel:)
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 != '' && !File.exist?(template_filename)
22
- raise ArgumentError, 'channel is nil' if channel.nil?
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 != '' ? File.read(template_filename) : '<%= yield %>'
27
- @template_filename = 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
- render_content
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, @view_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, 'read_data was called before rendering' if @rendered_data.nil?
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
@@ -1,36 +1,42 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- lib = File.expand_path('lib', __dir__)
3
+ lib = File.expand_path("lib", __dir__)
4
4
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
- require 'noticent/version'
5
+ require "noticent/version"
6
6
 
7
7
  Gem::Specification.new do |spec|
8
- spec.name = 'noticent'
9
- spec.version = Noticent::VERSION
10
- spec.authors = ['Khash Sajadi']
11
- spec.email = ['khash@cloud66.com']
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 = 'Act as Notified is a flexible framework to add notifications to a Rails application'
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 = 'exe'
21
- spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
22
- spec.require_paths = ['lib']
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 'activerecord', '~> 5.2'
25
- spec.add_dependency 'activesupport', '~> 5.2'
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 'bundler', '~> 2.0'
28
- spec.add_development_dependency 'factory_bot', '~> 5.0'
29
- spec.add_development_dependency 'generator_spec', '~> 0.9'
30
- spec.add_development_dependency 'rake', '~> 10.0'
31
- spec.add_development_dependency 'rspec', '~> 3.8'
32
- spec.add_development_dependency 'rubocop', '~> 0.69'
33
- spec.add_development_dependency 'rubocop-performance', '~> 1.3'
34
- spec.add_development_dependency 'rufo', '~> 0.7'
35
- spec.add_development_dependency 'sqlite3', '~> 1.4'
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
@@ -8,6 +8,7 @@ module Noticent
8
8
  end
9
9
 
10
10
  def some_event
11
+ @some_value = 1
11
12
  render
12
13
  end
13
14
 
@@ -0,0 +1,9 @@
1
+ module Noticent
2
+ module Testing
3
+ class Exclusive < ::Noticent::Channel
4
+ def only_here
5
+ render
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module Noticent
2
+ module Testing
3
+ class Simple < ::Noticent::Channel
4
+ def some_event
5
+ render
6
+ end
7
+ end
8
+ end
9
+ end
@@ -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