splitclient-rb 8.4.0 → 8.11.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.
Files changed (68) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +9 -2
  3. data/CHANGES.txt +24 -0
  4. data/LICENSE +169 -13
  5. data/NOTICE.txt +5 -0
  6. data/lib/splitclient-rb/cache/fetchers/split_fetcher.rb +9 -7
  7. data/lib/splitclient-rb/cache/filter/bloom_filter.rb +10 -6
  8. data/lib/splitclient-rb/cache/repositories/impressions/redis_repository.rb +1 -0
  9. data/lib/splitclient-rb/cache/repositories/rule_based_segments_repository.rb +136 -0
  10. data/lib/splitclient-rb/cache/repositories/segments_repository.rb +21 -2
  11. data/lib/splitclient-rb/cache/repositories/splits_repository.rb +42 -12
  12. data/lib/splitclient-rb/cache/senders/impressions_formatter.rb +24 -10
  13. data/lib/splitclient-rb/cache/stores/localhost_split_builder.rb +2 -1
  14. data/lib/splitclient-rb/clients/split_client.rb +146 -62
  15. data/lib/splitclient-rb/engine/api/client.rb +8 -0
  16. data/lib/splitclient-rb/engine/api/faraday_middleware/gzip.rb +1 -0
  17. data/lib/splitclient-rb/engine/api/splits.rb +100 -24
  18. data/lib/splitclient-rb/engine/auth_api_client.rb +3 -3
  19. data/lib/splitclient-rb/engine/common/impressions_manager.rb +56 -30
  20. data/lib/splitclient-rb/engine/events/events_delivery.rb +20 -0
  21. data/lib/splitclient-rb/engine/events/events_manager.rb +194 -0
  22. data/lib/splitclient-rb/engine/events/events_manager_config.rb +96 -0
  23. data/lib/splitclient-rb/engine/events/events_task.rb +50 -0
  24. data/lib/splitclient-rb/engine/events/noop_events_queue.rb +13 -0
  25. data/lib/splitclient-rb/engine/fallback_treatment_calculator.rb +48 -0
  26. data/lib/splitclient-rb/engine/impressions/unique_keys_tracker.rb +58 -11
  27. data/lib/splitclient-rb/engine/matchers/combining_matcher.rb +3 -1
  28. data/lib/splitclient-rb/engine/matchers/prerequisites_matcher.rb +31 -0
  29. data/lib/splitclient-rb/engine/matchers/rule_based_segment_matcher.rb +78 -0
  30. data/lib/splitclient-rb/engine/models/evaluation_options.rb +9 -0
  31. data/lib/splitclient-rb/engine/models/event_active_subscriptions.rb +14 -0
  32. data/lib/splitclient-rb/engine/models/events_metadata.rb +10 -0
  33. data/lib/splitclient-rb/engine/models/fallback_treatment.rb +11 -0
  34. data/lib/splitclient-rb/engine/models/fallback_treatments_configuration.rb +36 -0
  35. data/lib/splitclient-rb/engine/models/label.rb +1 -0
  36. data/lib/splitclient-rb/engine/models/sdk_event.rb +4 -0
  37. data/lib/splitclient-rb/engine/models/sdk_event_type.rb +4 -0
  38. data/lib/splitclient-rb/engine/models/sdk_internal_event.rb +8 -0
  39. data/lib/splitclient-rb/engine/models/sdk_internal_event_notification.rb +14 -0
  40. data/lib/splitclient-rb/engine/models/segment_type.rb +4 -0
  41. data/lib/splitclient-rb/engine/models/split_http_response.rb +19 -0
  42. data/lib/splitclient-rb/engine/models/valid_sdk_event.rb +14 -0
  43. data/lib/splitclient-rb/engine/parser/condition.rb +9 -0
  44. data/lib/splitclient-rb/engine/parser/evaluator.rb +24 -30
  45. data/lib/splitclient-rb/engine/status_manager.rb +7 -1
  46. data/lib/splitclient-rb/engine/sync_manager.rb +6 -6
  47. data/lib/splitclient-rb/engine/synchronizer.rb +37 -17
  48. data/lib/splitclient-rb/helpers/evaluator_helper.rb +37 -0
  49. data/lib/splitclient-rb/helpers/repository_helper.rb +39 -1
  50. data/lib/splitclient-rb/helpers/util.rb +12 -3
  51. data/lib/splitclient-rb/managers/split_manager.rb +2 -1
  52. data/lib/splitclient-rb/spec.rb +1 -1
  53. data/lib/splitclient-rb/split_config.rb +45 -5
  54. data/lib/splitclient-rb/split_factory.rb +27 -21
  55. data/lib/splitclient-rb/sse/event_source/client.rb +86 -29
  56. data/lib/splitclient-rb/sse/event_source/event_parser.rb +2 -2
  57. data/lib/splitclient-rb/sse/event_source/event_types.rb +1 -0
  58. data/lib/splitclient-rb/sse/notification_manager_keeper.rb +3 -3
  59. data/lib/splitclient-rb/sse/notification_processor.rb +3 -1
  60. data/lib/splitclient-rb/sse/workers/segments_worker.rb +4 -4
  61. data/lib/splitclient-rb/sse/workers/splits_worker.rb +63 -24
  62. data/lib/splitclient-rb/utilitites.rb +6 -10
  63. data/lib/splitclient-rb/validators.rb +56 -25
  64. data/lib/splitclient-rb/version.rb +1 -1
  65. data/lib/splitclient-rb.rb +22 -0
  66. data/sonar-project.properties +1 -0
  67. data/splitclient-rb.gemspec +3 -4
  68. metadata +32 -38
@@ -31,8 +31,11 @@ module SplitIoClient
31
31
  ],
32
32
  label: "targeting rule type unsupported by sdk"
33
33
  }]
34
+ TILL_PREFIX = '.splits.till'
35
+ SPLIT_PREFIX = '.split.'
36
+ READY_PREFIX = '.splits.ready'
34
37
 
35
- def initialize(config, flag_sets_repository, flag_set_filter)
38
+ def initialize(config, flag_sets_repository, flag_set_filter, internal_events_queue)
36
39
  super(config)
37
40
  @tt_cache = {}
38
41
  @adapter = case @config.cache_adapter.class.to_s
@@ -43,16 +46,26 @@ module SplitIoClient
43
46
  end
44
47
  @flag_sets = flag_sets_repository
45
48
  @flag_set_filter = flag_set_filter
46
- unless @config.mode.equal?(:consumer)
47
- @adapter.set_string(namespace_key('.splits.till'), '-1')
48
- @adapter.initialize_map(namespace_key('.segments.registered'))
49
- end
49
+ @internal_events_queue = internal_events_queue
50
+ initialize_keys
50
51
  end
51
52
 
52
53
  def update(to_add, to_delete, new_change_number)
53
54
  to_add.each{ |feature_flag| add_feature_flag(feature_flag) }
54
55
  to_delete.each{ |feature_flag| remove_feature_flag(feature_flag) }
55
56
  set_change_number(new_change_number)
57
+
58
+ if to_add.length > 0 || to_delete.length > 0
59
+ @internal_events_queue.push(
60
+ SplitIoClient::Engine::Models::SdkInternalEventNotification.new(
61
+ SplitIoClient::Engine::Models::SdkInternalEvent::FLAGS_UPDATED,
62
+ SplitIoClient::Engine::Models::EventsMetadata.new(
63
+ SplitIoClient::Engine::Models::SdkEventType::FLAG_UPDATE,
64
+ to_add.map {|flag| flag[:name]} | to_delete.map {|flag| flag[:name]}
65
+ )
66
+ )
67
+ )
68
+ end
56
69
  end
57
70
 
58
71
  def get_split(name)
@@ -87,16 +100,16 @@ module SplitIoClient
87
100
 
88
101
  # Return an array of Split Names excluding control keys like splits.till
89
102
  def split_names
90
- @adapter.find_strings_by_prefix(namespace_key('.split.'))
91
- .map { |split| split.gsub(namespace_key('.split.'), '') }
103
+ @adapter.find_strings_by_prefix(namespace_key(SPLIT_PREFIX))
104
+ .map { |split| split.gsub(namespace_key(SPLIT_PREFIX), '') }
92
105
  end
93
106
 
94
107
  def set_change_number(since)
95
- @adapter.set_string(namespace_key('.splits.till'), since)
108
+ @adapter.set_string(namespace_key(TILL_PREFIX), since)
96
109
  end
97
110
 
98
111
  def get_change_number
99
- @adapter.string(namespace_key('.splits.till'))
112
+ @adapter.string(namespace_key(TILL_PREFIX))
100
113
  end
101
114
 
102
115
  def set_segment_names(names)
@@ -112,21 +125,22 @@ module SplitIoClient
112
125
  end
113
126
 
114
127
  def ready?
115
- @adapter.string(namespace_key('.splits.ready')).to_i != -1
128
+ @adapter.string(namespace_key(READY_PREFIX)).to_i != -1
116
129
  end
117
130
 
118
131
  def not_ready!
119
- @adapter.set_string(namespace_key('.splits.ready'), -1)
132
+ @adapter.set_string(namespace_key(READY_PREFIX), -1)
120
133
  end
121
134
 
122
135
  def ready!
123
- @adapter.set_string(namespace_key('.splits.ready'), Time.now.utc.to_i)
136
+ @adapter.set_string(namespace_key(READY_PREFIX), Time.now.utc.to_i)
124
137
  end
125
138
 
126
139
  def clear
127
140
  @tt_cache.clear
128
141
 
129
142
  @adapter.clear(namespace_key)
143
+ initialize_keys
130
144
  end
131
145
 
132
146
  def kill(change_number, split_name, default_treatment)
@@ -139,6 +153,15 @@ module SplitIoClient
139
153
  split[:changeNumber] = change_number
140
154
 
141
155
  @adapter.set_string(namespace_key(".split.#{split_name}"), split.to_json)
156
+ @internal_events_queue.push(
157
+ SplitIoClient::Engine::Models::SdkInternalEventNotification.new(
158
+ SplitIoClient::Engine::Models::SdkInternalEvent::FLAG_KILLED_NOTIFICATION,
159
+ SplitIoClient::Engine::Models::EventsMetadata.new(
160
+ SplitIoClient::Engine::Models::SdkEventType::FLAG_UPDATE,
161
+ [split_name]
162
+ )
163
+ )
164
+ )
142
165
  end
143
166
 
144
167
  def splits_count
@@ -167,6 +190,13 @@ module SplitIoClient
167
190
 
168
191
  private
169
192
 
193
+ def initialize_keys
194
+ unless @config.mode.equal?(:consumer)
195
+ @adapter.set_string(namespace_key(TILL_PREFIX), '-1')
196
+ @adapter.initialize_map(namespace_key('.segments.registered'))
197
+ end
198
+ end
199
+
170
200
  def add_feature_flag(split)
171
201
  return unless split[:name]
172
202
  existing_split = get_split(split[:name])
@@ -37,15 +37,28 @@ module SplitIoClient
37
37
 
38
38
  def current_impressions(feature_impressions)
39
39
  feature_impressions.map do |impression|
40
- {
41
- k: impression[:i][:k],
42
- t: impression[:i][:t],
43
- m: impression[:i][:m],
44
- b: impression[:i][:b],
45
- r: impression[:i][:r],
46
- c: impression[:i][:c],
47
- pt: impression[:i][:pt]
48
- }
40
+ if impression[:i][:properties].nil?
41
+ impression = {
42
+ k: impression[:i][:k],
43
+ t: impression[:i][:t],
44
+ m: impression[:i][:m],
45
+ b: impression[:i][:b],
46
+ r: impression[:i][:r],
47
+ c: impression[:i][:c],
48
+ pt: impression[:i][:pt]
49
+ }
50
+ else
51
+ impression = {
52
+ k: impression[:i][:k],
53
+ t: impression[:i][:t],
54
+ m: impression[:i][:m],
55
+ b: impression[:i][:b],
56
+ r: impression[:i][:r],
57
+ c: impression[:i][:c],
58
+ pt: impression[:i][:pt],
59
+ properties: impression[:i][:properties].to_json.to_s
60
+ }
61
+ end
49
62
  end
50
63
  end
51
64
 
@@ -73,7 +86,8 @@ module SplitIoClient
73
86
  "#{impression[:i][:b]}:" \
74
87
  "#{impression[:i][:c]}:" \
75
88
  "#{impression[:i][:t]}:" \
76
- "#{impression[:i][:pt]}"
89
+ "#{impression[:i][:pt]}" \
90
+ "#{impression[:i][:properties]}" \
77
91
  end
78
92
  end
79
93
  end
@@ -22,7 +22,8 @@ module SplitIoClient
22
22
  seed: 2_089_907_429,
23
23
  defaultTreatment: 'control_treatment',
24
24
  configurations: build_configurations(treatments),
25
- conditions: build_conditions(treatments)
25
+ conditions: build_conditions(treatments),
26
+ prerequisites: []
26
27
  }
27
28
  end
28
29
 
@@ -18,10 +18,11 @@ module SplitIoClient
18
18
  # @param sdk_key [String] the SDK key for your split account
19
19
  #
20
20
  # @return [SplitIoClient] split.io client instance
21
- def initialize(sdk_key, repositories, status_manager, config, impressions_manager, telemetry_evaluation_producer, evaluator, split_validator)
21
+ def initialize(sdk_key, repositories, status_manager, config, impressions_manager, telemetry_evaluation_producer, evaluator, split_validator, fallback_treatment_calculator, events_manager)
22
22
  @api_key = sdk_key
23
23
  @splits_repository = repositories[:splits]
24
24
  @segments_repository = repositories[:segments]
25
+ @rule_based_segments_repository = repositories[:rule_based_segments]
25
26
  @impressions_repository = repositories[:impressions]
26
27
  @events_repository = repositories[:events]
27
28
  @status_manager = status_manager
@@ -31,26 +32,33 @@ module SplitIoClient
31
32
  @telemetry_evaluation_producer = telemetry_evaluation_producer
32
33
  @split_validator = split_validator
33
34
  @evaluator = evaluator
35
+ @fallback_treatment_calculator = fallback_treatment_calculator
36
+ @events_manager = events_manager
34
37
  end
35
38
 
36
39
  def get_treatment(
37
- key, split_name, attributes = {}, split_data = nil, store_impressions = true,
40
+ key, split_name, attributes = {}, evaluation_options = nil, split_data = nil, store_impressions = nil,
38
41
  multiple = false, evaluator = nil
39
42
  )
40
- result = treatment(key, split_name, attributes, split_data, store_impressions, GET_TREATMENT, multiple)
43
+ log_deprecated_warning(GET_TREATMENT, evaluator, 'evaluator')
44
+
45
+ result = treatment(key, split_name, attributes, split_data, store_impressions, GET_TREATMENT, multiple, evaluation_options)
41
46
  return result.tap { |t| t.delete(:config) } if multiple
42
47
  result[:treatment]
43
48
  end
44
49
 
45
50
  def get_treatment_with_config(
46
- key, split_name, attributes = {}, split_data = nil, store_impressions = true,
51
+ key, split_name, attributes = {}, evaluation_options = nil, split_data = nil, store_impressions = nil,
47
52
  multiple = false, evaluator = nil
48
53
  )
49
- treatment(key, split_name, attributes, split_data, store_impressions, GET_TREATMENT_WITH_CONFIG, multiple)
54
+ log_deprecated_warning(GET_TREATMENT, evaluator, 'evaluator')
55
+ result = treatment(key, split_name, attributes, split_data, store_impressions, GET_TREATMENT_WITH_CONFIG, multiple, evaluation_options)
56
+
57
+ { :config => result[:config], :treatment => result[:treatment] }
50
58
  end
51
59
 
52
- def get_treatments(key, split_names, attributes = {})
53
- treatments = treatments(key, split_names, attributes)
60
+ def get_treatments(key, split_names, attributes = {}, evaluation_options = nil)
61
+ treatments = treatments(key, split_names, attributes, evaluation_options)
54
62
 
55
63
  return treatments if treatments.nil?
56
64
  keys = treatments.keys
@@ -58,51 +66,63 @@ module SplitIoClient
58
66
  Hash[keys.zip(treats)]
59
67
  end
60
68
 
61
- def get_treatments_with_config(key, split_names, attributes = {})
62
- treatments(key, split_names, attributes, GET_TREATMENTS_WITH_CONFIG)
69
+ def get_treatments_with_config(key, split_names, attributes = {}, evaluation_options = nil)
70
+ results = treatments(key, split_names, attributes, evaluation_options, GET_TREATMENTS_WITH_CONFIG)
71
+
72
+ results.map{|key, value|
73
+ [key, { treatment: value[:treatment], config: value[:config] }]
74
+ }.to_h
63
75
  end
64
76
 
65
- def get_treatments_by_flag_set(key, flag_set, attributes = {})
77
+ def get_treatments_by_flag_set(key, flag_set, attributes = {}, evaluation_options = nil)
66
78
  valid_flag_set = @split_validator.valid_flag_sets(GET_TREATMENTS_BY_FLAG_SET, [flag_set])
67
79
  split_names = @splits_repository.get_feature_flags_by_sets(valid_flag_set)
68
- treatments = treatments(key, split_names, attributes, GET_TREATMENTS_BY_FLAG_SET)
80
+ treatments = treatments(key, split_names, attributes, evaluation_options, GET_TREATMENTS_BY_FLAG_SET)
69
81
  return treatments if treatments.nil?
70
82
  keys = treatments.keys
71
83
  treats = treatments.map { |_,t| t[:treatment] }
72
84
  Hash[keys.zip(treats)]
73
85
  end
74
86
 
75
- def get_treatments_by_flag_sets(key, flag_sets, attributes = {})
87
+ def get_treatments_by_flag_sets(key, flag_sets, attributes = {}, evaluation_options = nil)
76
88
  valid_flag_set = @split_validator.valid_flag_sets(GET_TREATMENTS_BY_FLAG_SETS, flag_sets)
77
89
  split_names = @splits_repository.get_feature_flags_by_sets(valid_flag_set)
78
- treatments = treatments(key, split_names, attributes, GET_TREATMENTS_BY_FLAG_SETS)
90
+ treatments = treatments(key, split_names, attributes, evaluation_options, GET_TREATMENTS_BY_FLAG_SETS)
79
91
  return treatments if treatments.nil?
80
92
  keys = treatments.keys
81
93
  treats = treatments.map { |_,t| t[:treatment] }
82
94
  Hash[keys.zip(treats)]
83
95
  end
84
96
 
85
- def get_treatments_with_config_by_flag_set(key, flag_set, attributes = {})
97
+ def get_treatments_with_config_by_flag_set(key, flag_set, attributes = {}, evaluation_options = nil)
86
98
  valid_flag_set = @split_validator.valid_flag_sets(GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SET, [flag_set])
87
99
  split_names = @splits_repository.get_feature_flags_by_sets(valid_flag_set)
88
- treatments(key, split_names, attributes, GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SET)
100
+ results = treatments(key, split_names, attributes, evaluation_options, GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SET)
101
+
102
+ results.map{|key, value|
103
+ [key, { treatment: value[:treatment], config: value[:config] }]
104
+ }.to_h
89
105
  end
90
106
 
91
- def get_treatments_with_config_by_flag_sets(key, flag_sets, attributes = {})
107
+ def get_treatments_with_config_by_flag_sets(key, flag_sets, attributes = {}, evaluation_options = nil)
92
108
  valid_flag_set = @split_validator.valid_flag_sets(GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SETS, flag_sets)
93
109
  split_names = @splits_repository.get_feature_flags_by_sets(valid_flag_set)
94
- treatments(key, split_names, attributes, GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SETS)
110
+ results = treatments(key, split_names, attributes, evaluation_options, GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SETS)
111
+
112
+ results.map{|key, value|
113
+ [key, { treatment: value[:treatment], config: value[:config] }]
114
+ }.to_h
95
115
  end
96
116
 
97
117
  def destroy
98
118
  @config.logger.info('Split client shutdown started...') if @config.debug_enabled
99
119
  if !@config.cache_adapter.is_a?(SplitIoClient::Cache::Adapters::RedisAdapter) && @config.impressions_mode != :none &&
100
120
  (!@impressions_repository.empty? || !@events_repository.empty?)
101
- @config.logger.debug("Impressions and/or Events cache is not empty")
121
+ @config.logger.debug("Impressions and/or Events cache is not empty") if @config.debug_enabled
102
122
  # Adding small delay to ensure sender threads are fully running
103
123
  sleep(0.1)
104
124
  if !@config.threads.key?(:impressions_sender) || !@config.threads.key?(:events_sender)
105
- @config.logger.debug("Periodic data recording thread has not started yet, waiting for service startup.")
125
+ @config.logger.debug("Periodic data recording thread has not started yet, waiting for service startup.") if @config.debug_enabled
106
126
  @config.threads[:start_sdk].join(5) if @config.threads.key?(:start_sdk)
107
127
  end
108
128
  end
@@ -115,6 +135,7 @@ module SplitIoClient
115
135
 
116
136
  @splits_repository.clear
117
137
  @segments_repository.clear
138
+ @rule_based_segments_repository.clear
118
139
 
119
140
  SplitIoClient.load_factory_registry
120
141
  SplitIoClient.split_factory_registry.remove(@api_key)
@@ -133,10 +154,7 @@ module SplitIoClient
133
154
  if !properties.nil?
134
155
  properties, size = validate_properties(properties)
135
156
  properties_size += size
136
- if (properties_size > EVENTS_SIZE_THRESHOLD)
137
- @config.logger.error("The maximum size allowed for the properties is #{EVENTS_SIZE_THRESHOLD}. Current is #{properties_size}. Event not queued")
138
- return false
139
- end
157
+ return false unless check_properties_size(properties_size)
140
158
  end
141
159
 
142
160
  if ready? && !@config.localhost_mode && !@splits_repository.traffic_type_exists(traffic_type_name)
@@ -159,8 +177,24 @@ module SplitIoClient
159
177
  @status_manager.wait_until_ready(time) if @status_manager
160
178
  end
161
179
 
180
+ def register(sdk_event, handler)
181
+ @events_manager.register(sdk_event, handler)
182
+ end
183
+
184
+ def unregister(sdk_event, handler)
185
+ @events_manager.unregister(sdk_event)
186
+ end
187
+
162
188
  private
163
189
 
190
+ def check_properties_size(properties_size, msg = "Event not queued")
191
+ if (properties_size > EVENTS_SIZE_THRESHOLD)
192
+ @config.logger.error("The maximum size allowed for the properties is #{EVENTS_SIZE_THRESHOLD}. Current is #{properties_size}. #{msg}")
193
+ return false
194
+ end
195
+ return true
196
+ end
197
+
164
198
  def keys_from_key(key)
165
199
  case key
166
200
  when Hash
@@ -204,7 +238,7 @@ module SplitIoClient
204
238
  end
205
239
  end
206
240
 
207
- def validate_properties(properties)
241
+ def validate_properties(properties, method = 'Event')
208
242
  properties_count = 0
209
243
  size = 0
210
244
 
@@ -223,11 +257,21 @@ module SplitIoClient
223
257
  end
224
258
  }
225
259
 
226
- @config.logger.warn('Event has more than 300 properties. Some of them will be trimmed when processed') if properties_count > 300
260
+ @config.logger.warn("#{method} has more than 300 properties. Some of them will be trimmed when processed") if properties_count > 300
227
261
 
228
262
  return fixed_properties, size
229
263
  end
230
264
 
265
+ def validate_evaluation_options(evaluation_options)
266
+ if !evaluation_options.is_a?(SplitIoClient::Engine::Models::EvaluationOptions)
267
+ @config.logger.warn("Option #{evaluation_options} should be a EvaluationOptions type. Setting value to nil")
268
+ return nil, 0
269
+ end
270
+ evaluation_options.properties = evaluation_options.properties.transform_keys(&:to_sym)
271
+ evaluation_options.properties, size = validate_properties(evaluation_options.properties, 'Treatment')
272
+ return evaluation_options, size
273
+ end
274
+
231
275
  def valid_client
232
276
  if @destroyed
233
277
  @config.logger.error('Client has already been destroyed - no calls possible')
@@ -236,8 +280,7 @@ module SplitIoClient
236
280
  @config.valid_mode
237
281
  end
238
282
 
239
- def treatments(key, feature_flag_names, attributes = {}, calling_method = 'get_treatments')
240
- attributes = {} if attributes.nil?
283
+ def treatments(key, feature_flag_names, attributes = {}, evaluation_options = nil, calling_method = 'get_treatments')
241
284
  sanitized_feature_flag_names = sanitize_split_names(calling_method, feature_flag_names)
242
285
 
243
286
  if sanitized_feature_flag_names.nil?
@@ -253,11 +296,12 @@ module SplitIoClient
253
296
  bucketing_key, matching_key = keys_from_key(key)
254
297
  bucketing_key = bucketing_key ? bucketing_key.to_s : nil
255
298
  matching_key = matching_key ? matching_key.to_s : nil
299
+ attributes = parsed_attributes(attributes)
256
300
 
257
301
  if !@config.split_validator.valid_get_treatments_parameters(calling_method, key, sanitized_feature_flag_names, matching_key, bucketing_key, attributes)
258
302
  to_return = Hash.new
259
303
  sanitized_feature_flag_names.each {|name|
260
- to_return[name.to_sym] = control_treatment_with_config
304
+ to_return[name.to_sym] = check_fallback_treatment(name, '')
261
305
  }
262
306
  return to_return
263
307
  end
@@ -266,8 +310,12 @@ module SplitIoClient
266
310
  impressions = []
267
311
  to_return = Hash.new
268
312
  sanitized_feature_flag_names.each {|name|
269
- to_return[name.to_sym] = control_treatment_with_config
270
- impressions << @impressions_manager.build_impression(matching_key, bucketing_key, name.to_sym, control_treatment_with_config.merge({ label: Engine::Models::Label::NOT_READY }), { attributes: attributes, time: nil })
313
+ treatment_data = check_fallback_treatment(name, Engine::Models::Label::NOT_READY)
314
+ to_return[name.to_sym] = treatment_data
315
+
316
+ impressions << { :impression => @impressions_manager.build_impression(matching_key, bucketing_key, name.to_sym,
317
+ get_treatment_without_config(treatment_data), false, { attributes: attributes, time: nil },
318
+ evaluation_options), :disabled => false }
271
319
  }
272
320
  @impressions_manager.track(impressions)
273
321
  return to_return
@@ -278,7 +326,6 @@ module SplitIoClient
278
326
  valid_feature_flag_names << feature_flag_name unless feature_flag_name.nil?
279
327
  }
280
328
  start = Time.now
281
- impressions_total = []
282
329
 
283
330
  feature_flags = @splits_repository.splits(valid_feature_flag_names)
284
331
  treatments = Hash.new
@@ -287,19 +334,18 @@ module SplitIoClient
287
334
  if feature_flag.nil?
288
335
  @config.logger.warn("#{calling_method}: you passed #{key} that " \
289
336
  'does not exist in this environment, please double check what feature flags exist in the Split user interface')
290
- invalid_treatments[key] = control_treatment_with_config
337
+ invalid_treatments[key] = check_fallback_treatment(key, Engine::Models::Label::NOT_FOUND)
291
338
  next
292
339
  end
293
- treatments_labels_change_numbers, impressions = evaluate_treatment(feature_flag, key, bucketing_key, matching_key, attributes, calling_method)
294
- impressions_total.concat(impressions) unless impressions.nil?
340
+ treatments_labels_change_numbers, impressions = evaluate_treatment(feature_flag, key, bucketing_key, matching_key, attributes, calling_method, false, evaluation_options)
295
341
  treatments[key] =
296
342
  {
297
343
  treatment: treatments_labels_change_numbers[:treatment],
298
344
  config: treatments_labels_change_numbers[:config]
299
345
  }
346
+ @impressions_manager.track(impressions) unless impressions.empty?
300
347
  end
301
348
  record_latency(calling_method, start)
302
- @impressions_manager.track(impressions_total) unless impressions_total.empty?
303
349
 
304
350
  treatments.merge(invalid_treatments)
305
351
  end
@@ -313,14 +359,18 @@ module SplitIoClient
313
359
  # @param split_data [Hash] split data, when provided this method doesn't fetch splits_repository for the data
314
360
  # @param store_impressions [Boolean] impressions aren't stored if this flag is false
315
361
  # @return [String/Hash] Treatment as String or Hash of treatments in case of array of features
316
- def treatment(key, feature_flag_name, attributes = {}, split_data = nil, store_impressions = true,
317
- calling_method = 'get_treatment', multiple = false)
362
+ def treatment(key, feature_flag_name, attributes = {}, split_data = nil, store_impressions = nil,
363
+ calling_method = 'get_treatment', multiple = false, evaluation_options = nil)
364
+
365
+ log_deprecated_warning(calling_method, split_data, 'split_data')
366
+ log_deprecated_warning(calling_method, store_impressions, 'store_impressions')
367
+
318
368
  impressions = []
319
369
  bucketing_key, matching_key = keys_from_key(key)
320
370
 
321
371
  attributes = parsed_attributes(attributes)
322
372
 
323
- return parsed_treatment(control_treatment, multiple) unless valid_client && @config.split_validator.valid_get_treatment_parameters(calling_method, key, feature_flag_name, matching_key, bucketing_key, attributes)
373
+ return parsed_treatment(check_fallback_treatment(feature_flag_name, ""), multiple) unless valid_client && @config.split_validator.valid_get_treatment_parameters(calling_method, key, feature_flag_name, matching_key, bucketing_key, attributes)
324
374
 
325
375
  bucketing_key = bucketing_key ? bucketing_key.to_s : nil
326
376
  matching_key = matching_key.to_s
@@ -332,54 +382,54 @@ module SplitIoClient
332
382
  end
333
383
 
334
384
  feature_flag = @splits_repository.get_split(feature_flag_name)
335
- treatments, impressions = evaluate_treatment(feature_flag, feature_flag_name, bucketing_key, matching_key, attributes, calling_method, multiple)
385
+ treatments, impressions_decorator = evaluate_treatment(feature_flag, feature_flag_name, bucketing_key, matching_key, attributes, calling_method, multiple, evaluation_options)
336
386
 
337
- @impressions_manager.track(impressions) unless impressions.nil?
387
+ @impressions_manager.track(impressions_decorator) unless impressions_decorator.nil?
338
388
  treatments
339
389
  end
340
390
 
341
- def evaluate_treatment(feature_flag, feature_flag_name, bucketing_key, matching_key, attributes, calling_method, multiple = false)
342
- impressions = []
391
+ def log_deprecated_warning(calling_method, parameter, parameter_name)
392
+ @config.logger.warn("#{calling_method}: detected #{parameter_name} parameter used, this parameter is deprecated and its value is ignored.") unless parameter.nil?
393
+ end
394
+
395
+ def evaluate_treatment(feature_flag, feature_flag_name, bucketing_key, matching_key, attributes, calling_method, multiple = false, evaluation_options = nil)
396
+ impressions_decorator = []
343
397
  begin
344
398
  start = Time.now
345
399
  if feature_flag.nil? && ready?
346
400
  @config.logger.warn("#{calling_method}: you passed #{feature_flag_name} that " \
347
401
  'does not exist in this environment, please double check what feature flags exist in the Split user interface')
348
- return parsed_treatment(control_treatment.merge({ label: Engine::Models::Label::NOT_FOUND }), multiple), nil
402
+ return check_fallback_treatment(feature_flag_name, Engine::Models::Label::NOT_FOUND), nil
349
403
  end
350
- treatment_data =
404
+
351
405
  if !feature_flag.nil? && ready?
352
- @evaluator.evaluate_feature_flag(
406
+ treatment_data = @evaluator.evaluate_feature_flag(
353
407
  { bucketing_key: bucketing_key, matching_key: matching_key }, feature_flag, attributes
354
408
  )
409
+ impressions_disabled = feature_flag[:impressionsDisabled]
355
410
  else
356
411
  @config.logger.error("#{calling_method}: the SDK is not ready, results may be incorrect for feature flag #{feature_flag_name}. Make sure to wait for SDK readiness before using this method.")
357
- control_treatment.merge({ label: Engine::Models::Label::NOT_READY })
412
+ treatment_data = check_fallback_treatment(feature_flag_name, Engine::Models::Label::NOT_READY)
413
+ impressions_disabled = false
358
414
  end
359
415
 
416
+ evaluation_options, size = validate_evaluation_options(evaluation_options) unless evaluation_options.nil?
417
+ evaluation_options.properties = nil unless evaluation_options.nil? or check_properties_size((EVENT_AVERAGE_SIZE + size), "Properties are ignored")
418
+
360
419
  record_latency(calling_method, start)
361
- impression = @impressions_manager.build_impression(matching_key, bucketing_key, feature_flag_name, treatment_data, { attributes: attributes, time: nil })
362
- impressions << impression unless impression.nil?
420
+ impression_decorator = { :impression => @impressions_manager.build_impression(matching_key, bucketing_key, feature_flag_name, treatment_data, impressions_disabled, { attributes: attributes, time: nil }, evaluation_options), :disabled => impressions_disabled }
421
+ impressions_decorator << impression_decorator unless impression_decorator.nil?
363
422
  rescue StandardError => e
364
423
  @config.log_found_exception(__method__.to_s, e)
365
-
366
424
  record_exception(calling_method)
425
+ treatment_data = check_fallback_treatment(feature_flag_name, Engine::Models::Label::EXCEPTION)
426
+ impression_decorator = { :impression => @impressions_manager.build_impression(matching_key, bucketing_key, feature_flag_name, get_treatment_without_config(treatment_data), false, { attributes: attributes, time: nil }, evaluation_options), :disabled => false }
367
427
 
368
- impression = @impressions_manager.build_impression(matching_key, bucketing_key, feature_flag_name, control_treatment, { attributes: attributes, time: nil })
369
- impressions << impression unless impression.nil?
428
+ impressions_decorator << impression_decorator unless impression_decorator.nil?
370
429
 
371
- return parsed_treatment(control_treatment.merge({ label: Engine::Models::Label::EXCEPTION }), multiple), impressions
430
+ return parsed_treatment(treatment_data, multiple), impressions_decorator
372
431
  end
373
-
374
- return parsed_treatment(treatment_data, multiple), impressions
375
- end
376
-
377
- def control_treatment
378
- { treatment: Engine::Models::Treatment::CONTROL }
379
- end
380
-
381
- def control_treatment_with_config
382
- {:treatment => Engine::Models::Treatment::CONTROL, :config => nil}
432
+ return parsed_treatment(treatment_data, multiple), impressions_decorator
383
433
  end
384
434
 
385
435
  def variable_size(value)
@@ -442,5 +492,39 @@ module SplitIoClient
442
492
  @telemetry_evaluation_producer.record_exception(Telemetry::Domain::Constants::TRACK)
443
493
  end
444
494
  end
495
+
496
+ def check_fallback_treatment(feature_name, label)
497
+ return {
498
+ label: (label != '')? label : nil,
499
+ treatment: Engine::Models::Treatment::CONTROL,
500
+ config: nil,
501
+ change_number: nil
502
+ } unless feature_name.is_a?(Symbol) || feature_name.is_a?(String)
503
+
504
+ fallback_treatment = @fallback_treatment_calculator.resolve(feature_name.to_sym, label)
505
+
506
+ {
507
+ label: (label != '')? fallback_treatment.label : nil,
508
+ treatment: fallback_treatment.treatment,
509
+ config: get_fallback_config(fallback_treatment),
510
+ change_number: nil
511
+ }
512
+ end
513
+
514
+ def get_treatment_without_config(treatment)
515
+ {
516
+ label: treatment[:label],
517
+ treatment: treatment[:treatment],
518
+ }
519
+ end
520
+
521
+ def get_fallback_config(fallback_treatment)
522
+ if fallback_treatment.config != nil
523
+ return fallback_treatment.config
524
+ end
525
+
526
+ return nil
527
+ end
528
+
445
529
  end
446
530
  end
@@ -20,6 +20,11 @@ module SplitIoClient
20
20
 
21
21
  @config.split_logger.log_if_debug("GET #{url} proxy: #{api_client.proxy}")
22
22
  end
23
+
24
+ rescue Zlib::GzipFile::Error => e
25
+ @config.logger.warn("Incorrect formatted response exception: #{e}\n")
26
+ SplitIoClient::Engine::Models::SplitHttpResponse.new(400, '', true)
27
+
23
28
  rescue StandardError => e
24
29
  @config.logger.warn("#{e}\nURL:#{url}\nparams:#{params}")
25
30
  raise e, 'Split SDK failed to connect to backend to retrieve information', e.backtrace
@@ -50,6 +55,9 @@ module SplitIoClient
50
55
  raise e, 'Split SDK failed to connect to backend to post information', e.backtrace
51
56
  end
52
57
 
58
+ def sdk_url_overriden?
59
+ @config.sdk_url_overriden?
60
+ end
53
61
  private
54
62
 
55
63
  def api_client
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'faraday'
4
+ require 'stringio'
4
5
 
5
6
  module SplitIoClient
6
7
  module FaradayMiddleware