rox-rollout 4.7.1 → 5.0.2

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 (116) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +34 -13
  3. data/.editorconfig +12 -0
  4. data/.github/workflows/ruby.yml +20 -0
  5. data/.rubocop.yml +17 -0
  6. data/Gemfile +2 -2
  7. data/README.md +32 -0
  8. data/Rakefile +12 -9
  9. data/bin/console +3 -4
  10. data/e2e/container.rb +11 -14
  11. data/e2e/custom_props.rb +9 -9
  12. data/e2e/rox_e2e_test.rb +17 -19
  13. data/e2e/test_vars.rb +3 -6
  14. data/e2e-server/run_server.sh +12 -0
  15. data/e2e-server/server.rb +158 -0
  16. data/example/local.rb +40 -0
  17. data/lib/rox/core/analytics.rb +18 -0
  18. data/lib/rox/core/client/buid.rb +8 -8
  19. data/lib/rox/core/client/device_properties.rb +7 -1
  20. data/lib/rox/core/client/dynamic_api.rb +53 -15
  21. data/lib/rox/core/client/internal_flags.rb +14 -2
  22. data/lib/rox/core/client/sdk_settings.rb +1 -1
  23. data/lib/rox/core/configuration/configuration.rb +1 -1
  24. data/lib/rox/core/configuration/configuration_fetched_args.rb +2 -2
  25. data/lib/rox/core/configuration/configuration_fetched_invoker.rb +8 -3
  26. data/lib/rox/core/configuration/configuration_parser.rb +38 -37
  27. data/lib/rox/core/configuration/fetcher_error.rb +1 -1
  28. data/lib/rox/core/configuration/fetcher_status.rb +1 -1
  29. data/lib/rox/core/configuration/models/experiment_model.rb +1 -1
  30. data/lib/rox/core/configuration/models/target_group_model.rb +1 -1
  31. data/lib/rox/core/consts/build.rb +2 -2
  32. data/lib/rox/core/consts/environment.rb +10 -8
  33. data/lib/rox/core/consts/property_type.rb +16 -17
  34. data/lib/rox/core/context/merged_context.rb +2 -1
  35. data/lib/rox/core/core.rb +89 -38
  36. data/lib/rox/core/entities/default_flag_values.rb +10 -0
  37. data/lib/rox/core/entities/flag.rb +22 -7
  38. data/lib/rox/core/entities/flag_setter.rb +6 -6
  39. data/lib/rox/core/entities/rox_double.rb +11 -0
  40. data/lib/rox/core/entities/rox_int.rb +11 -0
  41. data/lib/rox/core/entities/{variant.rb → rox_string.rb} +20 -13
  42. data/lib/rox/core/error_handling/exception_trigger.rb +10 -0
  43. data/lib/rox/core/error_handling/userspace_handler_exception.rb +12 -0
  44. data/lib/rox/core/error_handling/userspace_unhandled_error_invoker.rb +41 -0
  45. data/lib/rox/core/impression/impression_args.rb +2 -2
  46. data/lib/rox/core/impression/impression_invoker.rb +41 -7
  47. data/lib/rox/core/impression/models/experiment.rb +1 -1
  48. data/lib/rox/core/impression/models/reporting_value.rb +10 -2
  49. data/lib/rox/core/logging/logging.rb +3 -3
  50. data/lib/rox/core/logging/no_op_logger.rb +1 -1
  51. data/lib/rox/core/network/configuration_fetcher.rb +2 -2
  52. data/lib/rox/core/network/configuration_fetcher_roxy.rb +2 -2
  53. data/lib/rox/core/network/configuration_fetcher_self_managed.rb +30 -0
  54. data/lib/rox/core/network/configuration_source.rb +1 -1
  55. data/lib/rox/core/network/request.rb +1 -1
  56. data/lib/rox/core/network/request_configuration_builder.rb +5 -5
  57. data/lib/rox/core/network/request_data.rb +1 -1
  58. data/lib/rox/core/network/response.rb +6 -8
  59. data/lib/rox/core/network/state_sender.rb +63 -19
  60. data/lib/rox/core/notifications/notification_listener.rb +4 -4
  61. data/lib/rox/core/properties/custom_property.rb +1 -1
  62. data/lib/rox/core/properties/custom_property_type.rb +1 -1
  63. data/lib/rox/core/properties/device_property.rb +2 -2
  64. data/lib/rox/core/properties/property_factory.rb +132 -0
  65. data/lib/rox/core/register/registerer.rb +13 -12
  66. data/lib/rox/core/reporting/error_reporter.rb +14 -13
  67. data/lib/rox/core/repositories/experiment_repository.rb +2 -4
  68. data/lib/rox/core/repositories/flag_repository.rb +7 -7
  69. data/lib/rox/core/repositories/roxx/experiments_extensions.rb +8 -8
  70. data/lib/rox/core/repositories/roxx/properties_extensions.rb +32 -7
  71. data/lib/rox/core/repositories/target_group_repository.rb +2 -4
  72. data/lib/rox/core/roxx/evaluation_result.rb +2 -0
  73. data/lib/rox/core/roxx/node.rb +1 -1
  74. data/lib/rox/core/roxx/parser.rb +35 -21
  75. data/lib/rox/core/roxx/regular_expression_extensions.rb +6 -3
  76. data/lib/rox/core/roxx/string_tokenizer.rb +3 -1
  77. data/lib/rox/core/roxx/symbols.rb +1 -1
  78. data/lib/rox/core/roxx/token_type.rb +1 -1
  79. data/lib/rox/core/roxx/tokenized_expression.rb +16 -12
  80. data/lib/rox/core/roxx/value_compare_extensions.rb +49 -29
  81. data/lib/rox/core/security/signature_verifier.rb +3 -3
  82. data/lib/rox/core/security/signature_verifier_mock.rb +12 -0
  83. data/lib/rox/core/utils/type_utils.rb +1 -1
  84. data/lib/rox/server/client/server_properties.rb +1 -1
  85. data/lib/rox/server/flags/normalize_flag_type.rb +25 -0
  86. data/lib/rox/server/flags/rox_double.rb +8 -0
  87. data/lib/rox/server/flags/rox_flag.rb +1 -1
  88. data/lib/rox/server/flags/rox_int.rb +8 -0
  89. data/lib/rox/server/flags/rox_string.rb +8 -0
  90. data/lib/rox/server/flags/server_entities_provider.rb +14 -4
  91. data/lib/rox/server/logging/server_logger.rb +2 -2
  92. data/lib/rox/server/rox_options.rb +39 -8
  93. data/lib/rox/server/rox_server.rb +84 -59
  94. data/lib/rox/version.rb +1 -1
  95. data/rox.gemspec +11 -9
  96. metadata +62 -33
  97. data/CODEOWNERS +0 -1
  98. data/README_DEVELOP.md +0 -25
  99. data/_archive/.document +0 -5
  100. data/_archive/.rspec +0 -1
  101. data/_archive/Gemfile +0 -15
  102. data/_archive/Gemfile.lock +0 -87
  103. data/_archive/README.md +0 -32
  104. data/_archive/README.rdoc +0 -19
  105. data/_archive/Rakefile +0 -50
  106. data/_archive/lib/expr_function_definition.rb +0 -52
  107. data/_archive/lib/function_definition.rb +0 -48
  108. data/_archive/lib/function_token.rb +0 -12
  109. data/_archive/lib/object_extends.rb +0 -12
  110. data/_archive/lib/ruby_interpreter.rb +0 -292
  111. data/_archive/lib/stack.rb +0 -48
  112. data/_archive/lib/string_extends.rb +0 -14
  113. data/_archive/spec/ruby_interpreter_spec.rb +0 -203
  114. data/_archive/spec/spec_helper.rb +0 -30
  115. data/_archive/spec/stack_spec.rb +0 -77
  116. data/lib/rox/server/flags/rox_variant.rb +0 -8
data/lib/rox/core/core.rb CHANGED
@@ -11,6 +11,7 @@ require 'rox/core/network/request_configuration_builder'
11
11
  require 'rox/core/network/request'
12
12
  require 'rox/core/network/configuration_fetcher'
13
13
  require 'rox/core/network/configuration_fetcher_roxy'
14
+ require 'rox/core/network/configuration_fetcher_self_managed'
14
15
  require 'rox/core/network/state_sender'
15
16
  require 'rox/core/notifications/notification_listener'
16
17
  require 'rox/core/register/registerer'
@@ -20,8 +21,11 @@ require 'rox/core/entities/flag_setter'
20
21
  require 'rox/core/impression/impression_invoker'
21
22
  require 'rox/core/reporting/error_reporter'
22
23
  require 'rox/core/security/signature_verifier'
24
+ require 'rox/core/security/signature_verifier_mock'
23
25
  require 'rox/core/utils/periodic_task'
24
26
  require 'rox/core/client/dynamic_api'
27
+ require 'rox/core/analytics'
28
+ require 'rox/core/error_handling/userspace_unhandled_error_invoker'
25
29
 
26
30
  module Rox
27
31
  module Core
@@ -31,14 +35,10 @@ module Rox
31
35
  @custom_property_repository = CustomPropertyRepository.new
32
36
  @target_group_repository = TargetGroupRepository.new
33
37
  @experiment_repository = ExperimentRepository.new
34
- @parser = Parser.new
38
+ @user_unhandled_error_invoker = Rox::Core::UserspaceUnhandledErrorInvoker.new
39
+ @parser = Parser.new(@user_unhandled_error_invoker)
35
40
 
36
- experiments_extensions = ExperimentsExtensions.new(@parser, @target_group_repository, @flag_repository, @experiment_repository)
37
- properties_extensions = PropertiesExtensions.new(@parser, @custom_property_repository)
38
- experiments_extensions.extend
39
- properties_extensions.extend
40
-
41
- @configuration_fetched_invoker = ConfigurationFetchedInvoker.new
41
+ @configuration_fetched_invoker = ConfigurationFetchedInvoker.new(@user_unhandled_error_invoker)
42
42
  @registerer = Registerer.new(@flag_repository)
43
43
 
44
44
  @sdk_settings = nil
@@ -51,64 +51,106 @@ module Rox
51
51
  @push_updates_listener = nil
52
52
  end
53
53
 
54
- def setup(sdk_settings, device_properties, rox_options)
54
+ def userspace_unhandled_error_handler=(handler)
55
+ @user_unhandled_error_invoker.handler = handler
56
+ end
57
+
58
+ def setup(sdk_settings, device_properties)
55
59
  @sdk_settings = sdk_settings
60
+ @rox_options = device_properties.rox_options
61
+
62
+ experiments_extensions = ExperimentsExtensions.new(@parser, @target_group_repository, @flag_repository,
63
+ @experiment_repository)
64
+ properties_extensions = PropertiesExtensions.new(@parser, @custom_property_repository, @rox_options.dynamic_property_rule_handler)
65
+ experiments_extensions.extend
66
+ properties_extensions.extend
56
67
 
57
- roxy_path = rox_options.nil? || rox_options.roxy_url.nil? ? nil : rox_options.roxy_url
68
+ roxy_path = @rox_options.nil? || @rox_options.roxy_url.nil? ? nil : @rox_options.roxy_url
58
69
 
59
- if roxy_path.nil?
60
- validate_api_key(sdk_settings&.api_key)
61
- end
70
+ validate_api_key(sdk_settings&.api_key) if roxy_path.nil?
62
71
 
63
72
  # TODO: Analytics.Analytics.Initialize(deviceProperties.RolloutKey, deviceProperties)
64
- @internal_flags = InternalFlags.new(@experiment_repository, @parser)
73
+ @internal_flags = InternalFlags.new(@experiment_repository, @parser, @rox_options)
65
74
 
75
+ @analytics_client = Analytics.new(sdk_settings.api_key).client
66
76
  # TODO: impressionInvoker = new ImpressionInvoker(internalFlags, customPropertyRepository, deviceProperties, Analytics.Analytics.Client, roxyPath != null);
67
- @impression_invoker = ImpressionInvoker.new(@internal_flags, @custom_property_repository, device_properties, nil, !roxy_path.nil?)
77
+ @impression_invoker = ImpressionInvoker.new(@internal_flags, @custom_property_repository, device_properties,
78
+ @analytics_client, !roxy_path.nil?, @user_unhandled_error_invoker)
68
79
  @flag_setter = FlagSetter.new(@flag_repository, @parser, @experiment_repository, @impression_invoker)
69
80
  buid = BUID.new(sdk_settings, device_properties, @flag_repository, @custom_property_repository)
70
81
 
71
- request_config_builder = RequestConfigurationBuilder.new(sdk_settings, buid, device_properties, roxy_path)
82
+ request_config_builder = RequestConfigurationBuilder.new(sdk_settings, buid, device_properties)
72
83
 
73
84
  client_request = Request.new
74
85
  err_reporter_request = Request.new
75
86
 
76
87
  @error_reporter = ErrorReporter.new(err_reporter_request, device_properties, buid)
77
88
 
78
- if roxy_path.nil?
79
- @configuration_fetcher = ConfigurationFetcher.new(request_config_builder, client_request, @configuration_fetched_invoker)
80
- @state_sender = StateSender.new(@sdk_settings, device_properties, @flag_repository, @custom_property_repository)
89
+ if @rox_options.self_managed?
90
+ @configuration_fetcher = ConfigurationFetcherSelfManaged.new(request_config_builder, client_request,
91
+ @configuration_fetched_invoker)
92
+ @state_sender = StateSender.new(@sdk_settings, device_properties, @flag_repository,
93
+ @custom_property_repository)
94
+ @flag_repository.register_flag_added_handler { @state_sender.delayed_send }
95
+ @custom_property_repository.register_property_added_handler { @state_sender.delayed_send }
96
+ elsif roxy_path.nil?
97
+ @configuration_fetcher = ConfigurationFetcher.new(request_config_builder, client_request,
98
+ @configuration_fetched_invoker)
99
+ @state_sender = StateSender.new(@sdk_settings, device_properties, @flag_repository,
100
+ @custom_property_repository)
81
101
  @flag_repository.register_flag_added_handler { @state_sender.delayed_send }
82
102
  @custom_property_repository.register_property_added_handler { @state_sender.delayed_send }
83
103
  else
84
- @configuration_fetcher = ConfigurationFetcherRoxy.new(request_config_builder, client_request, @configuration_fetched_invoker)
104
+ @configuration_fetcher = ConfigurationFetcherRoxy.new(request_config_builder, client_request,
105
+ @configuration_fetched_invoker)
85
106
  end
86
107
 
87
108
  configuration_fetched_handler = nil
88
- unless rox_options.nil?
89
- configuration_fetched_handler = rox_options.configuration_fetched_handler
90
- end
109
+ configuration_fetched_handler = @rox_options.configuration_fetched_handler unless @rox_options.nil?
91
110
 
92
111
  @configuration_fetched_invoker.register_fetched_handler(&wrap_configuration_fetched_handler(&configuration_fetched_handler))
93
112
 
94
- Thread.new do
113
+ @thread = Thread.new do
95
114
  Thread.current.report_on_exception = false if Thread.current.respond_to?(:report_on_exception)
96
115
  fetch
116
+ @state_sender&.send
97
117
 
98
- if !rox_options.nil? && !rox_options.impression_handler.nil?
99
- @impression_invoker.register_impression_handler(&rox_options.impression_handler)
118
+ if !@rox_options.nil? && !@rox_options.impression_handler.nil?
119
+ @impression_invoker.register_impression_handler(&@rox_options.impression_handler)
100
120
  end
101
121
 
102
- if !rox_options.nil? && !rox_options.fetch_interval.nil?
103
- PeriodicTask.run(rox_options.fetch_interval) { fetch }
122
+ if !@rox_options.nil? && !@rox_options.fetch_interval.nil?
123
+ PeriodicTask.run(@rox_options.fetch_interval) { fetch }
104
124
  end
105
125
  end
106
126
  end
107
127
 
128
+ def shutdown
129
+ return if @thread.nil?
130
+
131
+ Thread.kill(@thread)
132
+ @thread = nil
133
+
134
+ unless @push_updates_listener.nil?
135
+ @push_updates_listener.stop
136
+ @push_updates_listener = nil
137
+ end
138
+
139
+ return if @analytics_client.nil?
140
+
141
+ @analytics_client.flush
142
+ end
143
+
108
144
  def fetch
109
145
  return if @configuration_fetcher.nil?
110
146
 
111
- configuration_parser = ConfigurationParser.new(SignatureVerifier.new, @error_reporter, @configuration_fetched_invoker)
147
+ signature_verifier = if @rox_options.self_managed?
148
+ SignatureVerifierMock.new
149
+ else
150
+ SignatureVerifier.new
151
+ end
152
+ configuration_parser = ConfigurationParser.new(signature_verifier, @error_reporter,
153
+ @configuration_fetched_invoker)
112
154
  result = @configuration_fetcher.fetch
113
155
  return if result.nil?
114
156
 
@@ -121,17 +163,22 @@ module Rox
121
163
 
122
164
  has_changes = @last_configurations.nil? || @last_configurations.data != result.data
123
165
  @last_configurations = result
124
- @configuration_fetched_invoker.invoke(FetcherStatus::APPLIED_FROM_NETWORK, configuration.signature_date, has_changes)
166
+ @configuration_fetched_invoker.invoke(FetcherStatus::APPLIED_FROM_NETWORK, configuration.signature_date,
167
+ has_changes)
168
+ end
169
+
170
+ def register_with_namespace(namespace, rox_container)
171
+ @registerer.register_instance(rox_container, namespace)
125
172
  end
126
173
 
127
- def register(ns, rox_container)
128
- @registerer.register_instance(rox_container, ns)
174
+ def register(*args)
175
+ rox_container = args.pop
176
+ namespace = args.length == 1 ? args.pop : ''
177
+ @registerer.register_instance(rox_container, namespace)
129
178
  end
130
179
 
131
180
  def context=(context)
132
- @flag_repository.all_flags.each do |flag|
133
- flag.context = context
134
- end
181
+ @parser.global_context = context
135
182
  end
136
183
 
137
184
  def add_custom_property(property)
@@ -145,7 +192,7 @@ module Rox
145
192
  def wrap_configuration_fetched_handler(&handler)
146
193
  lambda do |args|
147
194
  start_or_stop_push_updated_listener unless args.fetcher_status == FetcherStatus::ERROR_FETCHED_FAILED
148
- handler.call(args) unless handler.nil?
195
+ handler&.call(args)
149
196
  end
150
197
  end
151
198
 
@@ -153,7 +200,7 @@ module Rox
153
200
  if @internal_flags.enabled?('rox.internal.pushUpdates')
154
201
  if @push_updates_listener.nil?
155
202
  @push_updates_listener = NotificationListener.new(Environment.notifications_path, @sdk_settings.api_key)
156
- @push_updates_listener.on 'changed' do |data|
203
+ @push_updates_listener.on 'changed' do |_data|
157
204
  fetch
158
205
  end
159
206
  @push_updates_listener.start
@@ -170,12 +217,16 @@ module Rox
170
217
  Rox::Core::DynamicApi.new(@flag_repository, entities_provider)
171
218
  end
172
219
 
220
+ def dump_state
221
+ @state_sender.dump_state
222
+ end
223
+
173
224
  def validate_api_key(api_key)
174
225
  valid_api_key_pattern = /^[a-f\d]{24}$/
175
226
  if api_key&.strip&.empty?
176
- raise ArgumentError.new('Blank Rollout api key - must be specified')
227
+ raise ArgumentError, 'Blank Rollout api key - must be specified'
177
228
  elsif !valid_api_key_pattern.match(api_key)
178
- raise ArgumentError.new('Illegal Rollout api key')
229
+ raise ArgumentError, 'Illegal Rollout api key'
179
230
  end
180
231
  end
181
232
  end
@@ -0,0 +1,10 @@
1
+ module Rox
2
+ module Core
3
+ class DefaultFlagValues
4
+ STRING = ''
5
+ INT = 0
6
+ FLOAT = 0.0
7
+ BOOLEAN = false
8
+ end
9
+ end
10
+ end
@@ -1,20 +1,35 @@
1
- require 'rox/core/entities/variant'
1
+ require 'rox/core/entities/rox_string'
2
+ require 'rox/core/entities/default_flag_values'
2
3
 
3
4
  module Rox
4
5
  module Core
5
- class Flag < Variant
6
+ class Flag < RoxString
6
7
  FLAG_TRUE_VALUE = 'true'.freeze
7
8
  FLAG_FALSE_VALUE = 'false'.freeze
8
9
 
9
- def initialize(default_value = false)
10
- super(default_value ? Flag::FLAG_TRUE_VALUE : Flag::FLAG_FALSE_VALUE, [Flag::FLAG_FALSE_VALUE, Flag::FLAG_TRUE_VALUE])
10
+ def initialize(default_value = DefaultFlagValues::BOOLEAN)
11
+ super(default_value ? Flag::FLAG_TRUE_VALUE : Flag::FLAG_FALSE_VALUE, [Flag::FLAG_FALSE_VALUE,
12
+ Flag::FLAG_TRUE_VALUE])
11
13
  end
12
14
 
13
15
  def enabled?(context)
14
- value(context) == Flag::FLAG_TRUE_VALUE
16
+ merged_context = MergedContext.new(@parser&.global_context, context)
17
+ value = internal_enabled?(merged_context)
18
+ if [true, false].include? value
19
+ send_impressions(value, merged_context)
20
+ return value
21
+ end
22
+
23
+ send_impressions(DefaultFlagValues::BOOLEAN, merged_context)
24
+ DefaultFlagValues.BOOLEAN
25
+ end
26
+
27
+ def value(context = nil)
28
+ merged_context = MergedContext.new(@parser&.global_context, context)
29
+ internal_value(merged_context, false)
15
30
  end
16
31
 
17
- def internal_enabled?(context, nil_instead_of_default)
32
+ def internal_enabled?(context, nil_instead_of_default = false)
18
33
  val = internal_value(context, nil_instead_of_default)
19
34
  nil_instead_of_default && val.nil? ? nil : (val == Flag::FLAG_TRUE_VALUE)
20
35
  end
@@ -28,4 +43,4 @@ module Rox
28
43
  end
29
44
  end
30
45
  end
31
- end
46
+ end
@@ -7,9 +7,9 @@ module Rox
7
7
  @experiment_repository = experiment_repository
8
8
  @impression_invoker = impression_invoker
9
9
 
10
- @flag_repository.register_flag_added_handler do |variant|
11
- exp = @experiment_repository.experiment_by_flag(variant.name)
12
- set_flag_data(variant, exp)
10
+ @flag_repository.register_flag_added_handler do |string|
11
+ exp = @experiment_repository.experiment_by_flag(string.name)
12
+ set_flag_data(string, exp)
13
13
  end
14
14
  end
15
15
 
@@ -31,9 +31,9 @@ module Rox
31
31
  end
32
32
  end
33
33
 
34
- def set_flag_data(variant, experiment = nil)
35
- variant.set_for_evaluation(@parser, experiment, @impression_invoker)
34
+ def set_flag_data(string, experiment = nil)
35
+ string.set_for_evaluation(@parser, experiment, @impression_invoker)
36
36
  end
37
37
  end
38
38
  end
39
- end
39
+ end
@@ -0,0 +1,11 @@
1
+ require 'rox/core/entities/rox_string'
2
+
3
+ module Rox
4
+ module Core
5
+ class RoxDouble < RoxString
6
+ def value(context = nil)
7
+ internal_value(context, false, Float)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ require 'rox/core/entities/rox_string'
2
+
3
+ module Rox
4
+ module Core
5
+ class RoxInt < RoxString
6
+ def value(context = nil)
7
+ internal_value(context, false, Integer)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -4,22 +4,31 @@ require 'rox/core/impression/models/reporting_value'
4
4
 
5
5
  module Rox
6
6
  module Core
7
- class Variant
8
- attr_accessor :default_value, :options, :name, :context, :condition, :parser, :impression_invoker, :client_experiment
7
+ class RoxString
8
+ attr_accessor :default_value, :options, :name, :condition, :parser, :impression_invoker,
9
+ :client_experiment
9
10
 
10
11
  def initialize(default_value, options = [])
11
12
  @default_value = default_value
12
13
  @options = options.clone
14
+ raise ArgumentError, 'options should be an array' unless options.is_a?(Array)
15
+ if options.length > 0 && options.any? { |x| x.class != default_value.class }
16
+ raise ArgumentError, 'options should be the same type as default value'
17
+ end
13
18
  @options << default_value unless options.include?(default_value)
14
19
 
15
20
  @condition = nil
16
21
  @parser = nil
17
- @context = nil
18
22
  @impression_invoker = nil
19
23
  @client_experiment = nil
20
24
  @name = nil
21
25
  end
22
26
 
27
+ def send_impressions(return_value, merged_context)
28
+ reporting_value = ReportingValue.new(@name, return_value, @client_experiment)
29
+ @impression_invoker&.invoke(reporting_value, @client_experiment, merged_context)
30
+ end
31
+
23
32
  def set_for_evaluation(parser, experiment, impression_invoker)
24
33
  if experiment.nil?
25
34
  @client_experiment = nil
@@ -37,24 +46,22 @@ module Rox
37
46
  internal_value(context, false)
38
47
  end
39
48
 
40
- def internal_value(context, nil_instead_of_default)
49
+ def internal_value(context, nil_instead_of_default, evaluated_type = String)
41
50
  return_value = nil_instead_of_default ? nil : @default_value
42
- merged_context = MergedContext.new(@context, context)
51
+ merged_context = MergedContext.new(@parser&.global_context, context)
43
52
 
44
- if !@parser.nil? && !@condition.nil? && !@condition.empty?
53
+ unless @parser.nil? || @condition.nil? || @condition.empty?
45
54
  evaluation_result = @parser.evaluate_expression(@condition, merged_context)
46
55
  unless evaluation_result.nil?
47
- value = evaluation_result.string_value
48
- if !value.nil? && !value.empty?
49
- return_value = value
50
- end
56
+ value = evaluated_type == String ? evaluation_result.string_value : evaluation_result.value
57
+ is_empty = value.is_a?(String) && value.empty?
58
+ return_value = value if !value.nil? && !is_empty
51
59
  end
52
60
  end
53
61
 
54
- @impression_invoker.invoke(ReportingValue.new(@name, return_value), @client_experiment, merged_context) if @impression_invoker != nil
55
-
62
+ send_impressions(return_value, merged_context)
56
63
  return_value
57
64
  end
58
65
  end
59
66
  end
60
- end
67
+ end
@@ -0,0 +1,10 @@
1
+ module Rox
2
+ module Core
3
+ module ExceptionTrigger
4
+ DYNAMIC_PROPERTIES_RULE = 1
5
+ CONFIGURATION_FETCHED_HANDLER = 2
6
+ IMPRESSION_HANDLER = 3
7
+ CUSTOM_PROPERTY_GENERATOR = 4
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,12 @@
1
+ module Rox
2
+ module Core
3
+ class UserspaceHandlerException < StandardError
4
+ def initialize(exception_source, exception_trigger, exception)
5
+ @exception_source = exception_source
6
+ @exception_trigger = exception_trigger
7
+ @exception = exception
8
+ super('user unhandled exception in roxx expression')
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,41 @@
1
+ require 'rox/core/logging/logging'
2
+
3
+ module Rox
4
+ module Core
5
+ class UserspaceUnhandledErrorInvoker
6
+ def initialize(user_unhandler_error_handler = nil)
7
+ @user_unhandler_error_handler = user_unhandler_error_handler
8
+ end
9
+
10
+ def invoke(exception_source, exception_trigger, exception)
11
+ unless @user_unhandler_error_handler
12
+ Logging.logger.error("User Unhandled Error Occurred, no fallback handler was set, exception ignored: #{exception}")
13
+ return
14
+ end
15
+
16
+ begin
17
+ userspace_unhandled_error_args = UserspaceUnhandledErrorArgs.new(exception_source, exception_trigger, exception)
18
+ @user_unhandler_error_handler.call(userspace_unhandled_error_args)
19
+ rescue StandardError => e
20
+ Logging.logger.error("User Unhandled Error Handler itself threw an exception. original exception: #{e}")
21
+ end
22
+ end
23
+
24
+ def handler=(handler)
25
+ @user_unhandler_error_handler = handler
26
+ end
27
+ end
28
+
29
+ class UserspaceUnhandledErrorArgs
30
+ def initialize(exception_source = nil, exception_trigger = nil, exception = nil)
31
+ @exception_source = exception_source
32
+ @exception_trigger = exception_trigger
33
+ @exception = exception
34
+ end
35
+
36
+ def to_s
37
+ "UserspaceUnhandledErrorArgs(#{@exception_source}, #{@exception_trigger}, #{@exception})"
38
+ end
39
+ end
40
+ end
41
+ end
@@ -1,5 +1,5 @@
1
1
  module Rox
2
2
  module Core
3
- ImpressionArgs = Struct.new(:reporting_value, :experiment, :context)
3
+ ImpressionArgs = Struct.new(:reporting_value, :context)
4
4
  end
5
- end
5
+ end
@@ -1,23 +1,54 @@
1
1
  require 'rox/core/impression/impression_args'
2
+ require 'rox/core/logging/logging'
3
+ require 'rox/core/error_handling/exception_trigger'
4
+ require 'rox/core/consts/property_type'
2
5
 
3
6
  module Rox
4
7
  module Core
5
8
  class ImpressionInvoker
6
- def initialize(internal_flags, custom_property_repository, device_properties, analytics_client, is_roxy)
9
+ def initialize(internal_flags, custom_property_repository, device_properties, analytics_client, is_roxy, user_unhandled_error_invoker)
7
10
  @internal_flags = internal_flags
8
11
  @custom_property_repository = custom_property_repository
9
12
  @device_properties = device_properties
10
13
  @analytics_client = analytics_client
11
14
  @is_roxy = is_roxy
15
+ @user_unhandled_error_invoker = user_unhandled_error_invoker
12
16
 
13
17
  @impression_handlers = []
14
18
  @mutex = Mutex.new
15
19
  end
16
20
 
17
- def invoke(reporting_value, client_experiment, context)
18
- # TODO: Implement analytics logic
21
+ def invoke(reporting_value, stickiness_property, context)
22
+ begin
23
+ analytics_enabled = @internal_flags.enabled?('rox.internal.analytics')
24
+ if analytics_enabled && !@is_roxy
25
+ prop = @custom_property_repository.custom_property(stickiness_property) || @custom_property_repository.custom_property("rox.#{Rox::Core::PropertyType::DISTINCT_ID.name}")
26
+ distinct_id = '(null_distinct_id'
27
+ unless prop.nil?
28
+ prop_value = prop.value(context)
29
+ distinct_id = prop_value if prop_value.instance_of?(String)
30
+ end
19
31
 
20
- raise_impression_event(ImpressionArgs.new(reporting_value, client_experiment, context))
32
+ event_time = (Time.now.to_f * 1000.0).to_i
33
+ begin
34
+ event_time = ENV['rox.analytics.ms'].to_i
35
+ rescue StandardError
36
+ end
37
+
38
+ @analytics_client.track({
39
+ flag: reporting_value.name,
40
+ value: reporting_value.value,
41
+ distinctId: distinct_id,
42
+ experimentVersion: '0',
43
+ type: 'IMPRESSION',
44
+ time: event_time
45
+ })
46
+ end
47
+ rescue StandardError => ex
48
+ Logging.logger.error('Failed to send analytics', ex)
49
+ end
50
+
51
+ raise_impression_event(ImpressionArgs.new(reporting_value, context))
21
52
  end
22
53
 
23
54
  def register_impression_handler(&block)
@@ -32,10 +63,13 @@ module Rox
32
63
  handlers = @impression_handlers.clone
33
64
  end
34
65
 
35
- handlers.each do |handler|
36
- handler.call(args)
66
+ begin
67
+ handlers.each { |handler| handler.call(args) }
68
+ rescue StandardError => e
69
+ user_unhandled_error_invoker.invoke(handler, ExceptionTrigger::IMPRESSION_HANDLER, e)
70
+ Logging.logger.error('Impresssion handler exception', ex)
37
71
  end
38
72
  end
39
73
  end
40
74
  end
41
- end
75
+ end
@@ -11,4 +11,4 @@ module Rox
11
11
  end
12
12
  end
13
13
  end
14
- end
14
+ end
@@ -1,5 +1,13 @@
1
1
  module Rox
2
2
  module Core
3
- ReportingValue = Struct.new(:name, :value)
3
+ class ReportingValue
4
+ attr_reader :name, :value, :targeting
5
+
6
+ def initialize(name, value, experiment = nil)
7
+ @name = name
8
+ @value = value
9
+ @targeting = !experiment.nil?
10
+ end
11
+ end
4
12
  end
5
- end
13
+ end
@@ -5,8 +5,8 @@ module Rox
5
5
  class Logging
6
6
  @logger = nil
7
7
 
8
- def self.logger=(logger)
9
- @logger = logger
8
+ class << self
9
+ attr_writer :logger
10
10
  end
11
11
 
12
12
  def self.logger
@@ -14,4 +14,4 @@ module Rox
14
14
  end
15
15
  end
16
16
  end
17
- end
17
+ end
@@ -8,4 +8,4 @@ module Rox
8
8
  def warn(message, ex = nil); end
9
9
  end
10
10
  end
11
- end
11
+ end
@@ -19,8 +19,8 @@ module Rox
19
19
  end
20
20
 
21
21
  write_fetch_error_to_log_and_invoke_fetch_handler(source, fetch_result)
22
- rescue StandardError => ex
23
- write_fetch_exception_to_log_and_invoke_fetch_handler(source, ex)
22
+ rescue StandardError => e
23
+ write_fetch_exception_to_log_and_invoke_fetch_handler(source, e)
24
24
  end
25
25
 
26
26
  nil
@@ -14,8 +14,8 @@ module Rox
14
14
  else
15
15
  write_fetch_error_to_log_and_invoke_fetch_handler(source, fetch_roxy)
16
16
  end
17
- rescue StandardError => ex
18
- write_fetch_exception_to_log_and_invoke_fetch_handler(source, ex)
17
+ rescue StandardError => e
18
+ write_fetch_exception_to_log_and_invoke_fetch_handler(source, e)
19
19
  end
20
20
 
21
21
  nil
@@ -0,0 +1,30 @@
1
+ require 'rox/core/network/configuration_source'
2
+ require 'rox/core/network/configuration_fetch_result'
3
+ require 'rox/core/network/configuration_fetcher_base'
4
+
5
+ module Rox
6
+ module Core
7
+ class ConfigurationFetcherSelfManaged < ConfigurationFetcherBase
8
+ def fetch
9
+ source = ConfigurationSource::API
10
+ begin
11
+ fetch_result = fetch_from_api
12
+ if fetch_result.success?
13
+ return ConfigurationFetchResult.new(fetch_result.text, source)
14
+ else
15
+ write_fetch_error_to_log_and_invoke_fetch_handler(source, fetch_result)
16
+ end
17
+ rescue StandardError => e
18
+ write_fetch_exception_to_log_and_invoke_fetch_handler(source, e)
19
+ end
20
+
21
+ nil
22
+ end
23
+
24
+ def fetch_from_api
25
+ api_request = @request_configuration_builder.build_for_api
26
+ @request.send_post(api_request.url, api_request.query_params)
27
+ end
28
+ end
29
+ end
30
+ end