noticent 0.0.1.pre.pre → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -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