splitclient-rb 8.1.3.pre.rc4-java → 8.3.0.pre.rc1-java

Sign up to get free protection for your applications and to get access to all the features.
Files changed (28) hide show
  1. checksums.yaml +4 -4
  2. data/.github/CODEOWNERS +1 -0
  3. data/CHANGES.txt +3 -0
  4. data/lib/splitclient-rb/cache/fetchers/split_fetcher.rb +2 -29
  5. data/lib/splitclient-rb/cache/filter/flag_set_filter.rb +40 -0
  6. data/lib/splitclient-rb/cache/repositories/flag_sets/memory_repository.rb +40 -0
  7. data/lib/splitclient-rb/cache/repositories/flag_sets/redis_repository.rb +49 -0
  8. data/lib/splitclient-rb/cache/repositories/splits_repository.rb +100 -39
  9. data/lib/splitclient-rb/cache/stores/localhost_split_store.rb +1 -1
  10. data/lib/splitclient-rb/clients/split_client.rb +165 -81
  11. data/lib/splitclient-rb/engine/api/splits.rb +8 -3
  12. data/lib/splitclient-rb/engine/matchers/dependency_matcher.rb +1 -1
  13. data/lib/splitclient-rb/engine/parser/evaluator.rb +15 -21
  14. data/lib/splitclient-rb/exceptions.rb +11 -0
  15. data/lib/splitclient-rb/helpers/repository_helper.rb +23 -0
  16. data/lib/splitclient-rb/split_config.rb +22 -6
  17. data/lib/splitclient-rb/split_factory.rb +32 -9
  18. data/lib/splitclient-rb/sse/workers/splits_worker.rb +2 -9
  19. data/lib/splitclient-rb/telemetry/domain/constants.rb +4 -0
  20. data/lib/splitclient-rb/telemetry/domain/structs.rb +4 -4
  21. data/lib/splitclient-rb/telemetry/memory/memory_synchronizer.rb +18 -2
  22. data/lib/splitclient-rb/telemetry/redis/redis_synchronizer.rb +0 -1
  23. data/lib/splitclient-rb/telemetry/storages/memory.rb +12 -0
  24. data/lib/splitclient-rb/telemetry/synchronizer.rb +6 -2
  25. data/lib/splitclient-rb/validators.rb +64 -3
  26. data/lib/splitclient-rb/version.rb +1 -1
  27. data/lib/splitclient-rb.rb +4 -0
  28. metadata +7 -2
@@ -5,6 +5,10 @@ module SplitIoClient
5
5
  GET_TREATMENTS = 'get_treatments'
6
6
  GET_TREATMENT_WITH_CONFIG = 'get_treatment_with_config'
7
7
  GET_TREATMENTS_WITH_CONFIG = 'get_treatments_with_config'
8
+ GET_TREATMENTS_BY_FLAG_SET = 'get_treatments_by_flag_set'
9
+ GET_TREATMENTS_BY_FLAG_SETS = 'get_treatments_by_flag_sets'
10
+ GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SET = 'get_treatments_with_config_by_flag_set'
11
+ GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SETS = 'get_treatments_with_config_by_flag_sets'
8
12
  TRACK = 'track'
9
13
 
10
14
  class SplitClient
@@ -14,7 +18,7 @@ module SplitIoClient
14
18
  # @param sdk_key [String] the SDK key for your split account
15
19
  #
16
20
  # @return [SplitIoClient] split.io client instance
17
- def initialize(sdk_key, repositories, status_manager, config, impressions_manager, telemetry_evaluation_producer)
21
+ def initialize(sdk_key, repositories, status_manager, config, impressions_manager, telemetry_evaluation_producer, evaluator, split_validator)
18
22
  @api_key = sdk_key
19
23
  @splits_repository = repositories[:splits]
20
24
  @segments_repository = repositories[:segments]
@@ -25,36 +29,29 @@ module SplitIoClient
25
29
  @config = config
26
30
  @impressions_manager = impressions_manager
27
31
  @telemetry_evaluation_producer = telemetry_evaluation_producer
32
+ @split_validator = split_validator
33
+ @evaluator = evaluator
28
34
  end
29
35
 
30
36
  def get_treatment(
31
37
  key, split_name, attributes = {}, split_data = nil, store_impressions = true,
32
38
  multiple = false, evaluator = nil
33
39
  )
34
- impressions = []
35
- result = treatment(key, split_name, attributes, split_data, store_impressions, multiple, evaluator, GET_TREATMENT, impressions)
36
- @impressions_manager.track(impressions)
37
-
38
- if multiple
39
- result.tap { |t| t.delete(:config) }
40
- else
41
- result[:treatment]
42
- end
40
+ result = treatment(key, split_name, attributes, split_data, store_impressions, GET_TREATMENT, multiple)
41
+ return result.tap { |t| t.delete(:config) } if multiple
42
+ result[:treatment]
43
43
  end
44
44
 
45
45
  def get_treatment_with_config(
46
46
  key, split_name, attributes = {}, split_data = nil, store_impressions = true,
47
47
  multiple = false, evaluator = nil
48
48
  )
49
- impressions = []
50
- result = treatment(key, split_name, attributes, split_data, store_impressions, multiple, evaluator, GET_TREATMENT_WITH_CONFIG, impressions)
51
- @impressions_manager.track(impressions)
52
-
53
- result
49
+ treatment(key, split_name, attributes, split_data, store_impressions, GET_TREATMENT_WITH_CONFIG, multiple)
54
50
  end
55
51
 
56
52
  def get_treatments(key, split_names, attributes = {})
57
53
  treatments = treatments(key, split_names, attributes)
54
+
58
55
  return treatments if treatments.nil?
59
56
  keys = treatments.keys
60
57
  treats = treatments.map { |_,t| t[:treatment] }
@@ -65,6 +62,38 @@ module SplitIoClient
65
62
  treatments(key, split_names, attributes, GET_TREATMENTS_WITH_CONFIG)
66
63
  end
67
64
 
65
+ def get_treatments_by_flag_set(key, flag_set, attributes = {})
66
+ valid_flag_set = @split_validator.valid_flag_sets(GET_TREATMENTS_BY_FLAG_SET, [flag_set])
67
+ split_names = @splits_repository.get_feature_flags_by_sets(valid_flag_set)
68
+ treatments = treatments(key, split_names, attributes, GET_TREATMENTS_BY_FLAG_SET)
69
+ return treatments if treatments.nil?
70
+ keys = treatments.keys
71
+ treats = treatments.map { |_,t| t[:treatment] }
72
+ Hash[keys.zip(treats)]
73
+ end
74
+
75
+ def get_treatments_by_flag_sets(key, flag_sets, attributes = {})
76
+ valid_flag_set = @split_validator.valid_flag_sets(GET_TREATMENTS_BY_FLAG_SETS, flag_sets)
77
+ split_names = @splits_repository.get_feature_flags_by_sets(valid_flag_set)
78
+ treatments = treatments(key, split_names, attributes, GET_TREATMENTS_BY_FLAG_SETS)
79
+ return treatments if treatments.nil?
80
+ keys = treatments.keys
81
+ treats = treatments.map { |_,t| t[:treatment] }
82
+ Hash[keys.zip(treats)]
83
+ end
84
+
85
+ def get_treatments_with_config_by_flag_set(key, flag_set, attributes = {})
86
+ valid_flag_set = @split_validator.valid_flag_sets(GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SET, [flag_set])
87
+ 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)
89
+ end
90
+
91
+ def get_treatments_with_config_by_flag_sets(key, flag_sets, attributes = {})
92
+ valid_flag_set = @split_validator.valid_flag_sets(GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SETS, flag_sets)
93
+ 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)
95
+ end
96
+
68
97
  def destroy
69
98
  @config.logger.info('Split client shutdown started...') if @config.debug_enabled
70
99
 
@@ -117,6 +146,12 @@ module SplitIoClient
117
146
  false
118
147
  end
119
148
 
149
+ def block_until_ready(time = nil)
150
+ @status_manager.wait_until_ready(time) if @status_manager
151
+ end
152
+
153
+ private
154
+
120
155
  def keys_from_key(key)
121
156
  case key
122
157
  when Hash
@@ -126,7 +161,7 @@ module SplitIoClient
126
161
  end
127
162
  end
128
163
 
129
- def parsed_treatment(multiple, treatment_data)
164
+ def parsed_treatment(treatment_data, multiple = false)
130
165
  if multiple
131
166
  {
132
167
  treatment: treatment_data[:treatment],
@@ -135,16 +170,20 @@ module SplitIoClient
135
170
  config: treatment_data[:config]
136
171
  }
137
172
  else
138
- {
139
- treatment: treatment_data[:treatment],
140
- config: treatment_data[:config],
141
- }
173
+ {
174
+ treatment: treatment_data[:treatment],
175
+ config: treatment_data[:config],
176
+ }
142
177
  end
143
178
  end
144
179
 
145
180
  def sanitize_split_names(calling_method, split_names)
181
+ return nil if !split_names.is_a?(Array)
182
+
146
183
  split_names.compact.uniq.select do |split_name|
147
- if (split_name.is_a?(String) || split_name.is_a?(Symbol)) && !split_name.empty?
184
+ if split_name.nil?
185
+ false
186
+ elsif (split_name.is_a?(String) || split_name.is_a?(Symbol)) && !split_name.empty?
148
187
  true
149
188
  elsif split_name.is_a?(String) && split_name.empty?
150
189
  @config.logger.warn("#{calling_method}: you passed an empty feature_flag_name, flag name must be a non-empty String or a Symbol")
@@ -156,12 +195,6 @@ module SplitIoClient
156
195
  end
157
196
  end
158
197
 
159
- def block_until_ready(time = nil)
160
- @status_manager.wait_until_ready(time) if @status_manager
161
- end
162
-
163
- private
164
-
165
198
  def validate_properties(properties)
166
199
  properties_count = 0
167
200
  size = 0
@@ -194,39 +227,72 @@ module SplitIoClient
194
227
  @config.valid_mode
195
228
  end
196
229
 
197
- def treatments(key, split_names, attributes = {}, calling_method = 'get_treatments')
198
- return nil unless @config.split_validator.valid_get_treatments_parameters(calling_method, split_names)
230
+ def treatments(key, feature_flag_names, attributes = {}, calling_method = 'get_treatments')
231
+ attributes = {} if attributes.nil?
232
+ sanitized_feature_flag_names = sanitize_split_names(calling_method, feature_flag_names)
199
233
 
200
- sanitized_split_names = sanitize_split_names(calling_method, split_names)
234
+ if sanitized_feature_flag_names.nil?
235
+ @config.logger.error("#{calling_method}: feature_flag_names must be a non-empty Array")
236
+ return nil
237
+ end
201
238
 
202
- if sanitized_split_names.empty?
239
+ if sanitized_feature_flag_names.empty?
203
240
  @config.logger.error("#{calling_method}: feature_flag_names must be a non-empty Array")
204
241
  return {}
205
242
  end
206
243
 
207
244
  bucketing_key, matching_key = keys_from_key(key)
208
245
  bucketing_key = bucketing_key ? bucketing_key.to_s : nil
209
- matching_key = matching_key ? matching_key.to_s : nil
246
+ matching_key = matching_key ? matching_key.to_s : nil
210
247
 
211
- evaluator = Engine::Parser::Evaluator.new(@segments_repository, @splits_repository, @config, true)
212
- start = Time.now
213
- impressions = []
214
- treatments_labels_change_numbers =
215
- @splits_repository.get_splits(sanitized_split_names).each_with_object({}) do |(name, data), memo|
216
- memo.merge!(name => treatment(key, name, attributes, data, false, true, evaluator, calling_method, impressions))
217
- end
248
+ if !@config.split_validator.valid_get_treatments_parameters(calling_method, key, sanitized_feature_flag_names, matching_key, bucketing_key, attributes)
249
+ to_return = Hash.new
250
+ sanitized_feature_flag_names.each {|name|
251
+ to_return[name.to_sym] = control_treatment_with_config
252
+ }
253
+ return to_return
254
+ end
218
255
 
219
- record_latency(calling_method, start)
220
- @impressions_manager.track(impressions)
256
+ if !ready?
257
+ impressions = []
258
+ to_return = Hash.new
259
+ sanitized_feature_flag_names.each {|name|
260
+ to_return[name.to_sym] = control_treatment_with_config
261
+ 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 })
262
+ }
263
+ @impressions_manager.track(impressions)
264
+ return to_return
265
+ end
221
266
 
222
- split_names_keys = treatments_labels_change_numbers.keys
223
- treatments = treatments_labels_change_numbers.values.map do |v|
267
+ valid_feature_flag_names = []
268
+ sanitized_feature_flag_names.each { |feature_flag_name|
269
+ valid_feature_flag_names << feature_flag_name unless feature_flag_name.nil?
270
+ }
271
+ start = Time.now
272
+ impressions_total = []
273
+
274
+ feature_flags = @splits_repository.splits(valid_feature_flag_names)
275
+ treatments = Hash.new
276
+ invalid_treatments = Hash.new
277
+ feature_flags.each do |key, feature_flag|
278
+ if feature_flag.nil?
279
+ @config.logger.warn("#{calling_method}: you passed #{key} that " \
280
+ 'does not exist in this environment, please double check what feature flags exist in the Split user interface')
281
+ invalid_treatments[key] = control_treatment_with_config
282
+ next
283
+ end
284
+ treatments_labels_change_numbers, impressions = evaluate_treatment(feature_flag, key, bucketing_key, matching_key, attributes, calling_method)
285
+ impressions_total.concat(impressions) unless impressions.nil?
286
+ treatments[key] =
224
287
  {
225
- treatment: v[:treatment],
226
- config: v[:config]
288
+ treatment: treatments_labels_change_numbers[:treatment],
289
+ config: treatments_labels_change_numbers[:config]
227
290
  }
228
291
  end
229
- Hash[split_names_keys.zip(treatments)]
292
+ record_latency(calling_method, start)
293
+ @impressions_manager.track(impressions_total) unless impressions_total.empty?
294
+
295
+ treatments.merge(invalid_treatments)
230
296
  end
231
297
 
232
298
  #
@@ -237,72 +303,74 @@ module SplitIoClient
237
303
  # @param attributes [Hash] attributes to pass to the treatment class
238
304
  # @param split_data [Hash] split data, when provided this method doesn't fetch splits_repository for the data
239
305
  # @param store_impressions [Boolean] impressions aren't stored if this flag is false
240
- # @param multiple [Hash] internal flag to signal if method is called by get_treatments
241
- # @param evaluator [Evaluator] Evaluator class instance, used to cache treatments
242
- #
243
306
  # @return [String/Hash] Treatment as String or Hash of treatments in case of array of features
244
- def treatment(
245
- key, split_name, attributes = {}, split_data = nil, store_impressions = true,
246
- multiple = false, evaluator = nil, calling_method = 'get_treatment', impressions = []
247
- )
248
- control_treatment = { treatment: Engine::Models::Treatment::CONTROL }
249
-
307
+ def treatment(key, feature_flag_name, attributes = {}, split_data = nil, store_impressions = true,
308
+ calling_method = 'get_treatment', multiple = false)
309
+ impressions = []
250
310
  bucketing_key, matching_key = keys_from_key(key)
251
311
 
252
312
  attributes = parsed_attributes(attributes)
253
313
 
254
- return parsed_treatment(multiple, control_treatment) unless valid_client && @config.split_validator.valid_get_treatment_parameters(calling_method, key, split_name, matching_key, bucketing_key, attributes)
314
+ 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)
255
315
 
256
316
  bucketing_key = bucketing_key ? bucketing_key.to_s : nil
257
317
  matching_key = matching_key.to_s
258
- sanitized_split_name = split_name.to_s.strip
318
+ sanitized_feature_flag_name = feature_flag_name.to_s.strip
259
319
 
260
- if split_name.to_s != sanitized_split_name
261
- @config.logger.warn("#{calling_method}: feature_flag_name #{split_name} has extra whitespace, trimming")
262
- split_name = sanitized_split_name
320
+ if feature_flag_name.to_s != sanitized_feature_flag_name
321
+ @config.logger.warn("#{calling_method}: feature_flag_name #{feature_flag_name} has extra whitespace, trimming")
322
+ feature_flag_name = sanitized_feature_flag_name
263
323
  end
264
324
 
265
- evaluator ||= Engine::Parser::Evaluator.new(@segments_repository, @splits_repository, @config)
325
+ feature_flag = @splits_repository.get_split(feature_flag_name)
326
+ treatments, impressions = evaluate_treatment(feature_flag, feature_flag_name, bucketing_key, matching_key, attributes, calling_method, multiple)
266
327
 
328
+ @impressions_manager.track(impressions) unless impressions.nil?
329
+ treatments
330
+ end
331
+
332
+ def evaluate_treatment(feature_flag, feature_flag_name, bucketing_key, matching_key, attributes, calling_method, multiple = false)
333
+ impressions = []
267
334
  begin
268
335
  start = Time.now
269
-
270
- split = multiple ? split_data : @splits_repository.get_split(split_name)
271
-
272
- if split.nil? && ready?
273
- @config.logger.warn("#{calling_method}: you passed #{split_name} that " \
336
+ if feature_flag.nil? && ready?
337
+ @config.logger.warn("#{calling_method}: you passed #{feature_flag_name} that " \
274
338
  'does not exist in this environment, please double check what feature flags exist in the Split user interface')
275
-
276
- return parsed_treatment(multiple, control_treatment.merge({ label: Engine::Models::Label::NOT_FOUND }))
339
+ return parsed_treatment(control_treatment.merge({ label: Engine::Models::Label::NOT_FOUND }), multiple), nil
277
340
  end
278
-
279
341
  treatment_data =
280
- if !split.nil? && ready?
281
- evaluator.call(
282
- { bucketing_key: bucketing_key, matching_key: matching_key }, split, attributes
342
+ if !feature_flag.nil? && ready?
343
+ @evaluator.evaluate_feature_flag(
344
+ { bucketing_key: bucketing_key, matching_key: matching_key }, feature_flag, attributes
283
345
  )
284
346
  else
285
347
  @config.logger.error("#{calling_method}: the SDK is not ready, the operation cannot be executed")
286
-
287
348
  control_treatment.merge({ label: Engine::Models::Label::NOT_READY })
288
349
  end
289
350
 
290
- record_latency(calling_method, start) unless multiple
291
-
292
- impression = @impressions_manager.build_impression(matching_key, bucketing_key, split_name, treatment_data, { attributes: attributes, time: nil })
351
+ record_latency(calling_method, start)
352
+ impression = @impressions_manager.build_impression(matching_key, bucketing_key, feature_flag_name, treatment_data, { attributes: attributes, time: nil })
293
353
  impressions << impression unless impression.nil?
294
354
  rescue StandardError => e
295
355
  @config.log_found_exception(__method__.to_s, e)
296
356
 
297
357
  record_exception(calling_method)
298
358
 
299
- impression = @impressions_manager.build_impression(matching_key, bucketing_key, split_name, control_treatment, { attributes: attributes, time: nil })
359
+ impression = @impressions_manager.build_impression(matching_key, bucketing_key, feature_flag_name, control_treatment, { attributes: attributes, time: nil })
300
360
  impressions << impression unless impression.nil?
301
361
 
302
- return parsed_treatment(multiple, control_treatment.merge({ label: Engine::Models::Label::EXCEPTION }))
362
+ return parsed_treatment(control_treatment.merge({ label: Engine::Models::Label::EXCEPTION }), multiple), impressions
303
363
  end
304
364
 
305
- parsed_treatment(multiple, treatment_data)
365
+ return parsed_treatment(treatment_data, multiple), impressions
366
+ end
367
+
368
+ def control_treatment
369
+ { treatment: Engine::Models::Treatment::CONTROL }
370
+ end
371
+
372
+ def control_treatment_with_config
373
+ {:treatment => Engine::Models::Treatment::CONTROL, :config => nil}
306
374
  end
307
375
 
308
376
  def variable_size(value)
@@ -320,7 +388,7 @@ module SplitIoClient
320
388
 
321
389
  def record_latency(method, start)
322
390
  bucket = BinarySearchLatencyTracker.get_bucket((Time.now - start) * 1000.0)
323
-
391
+
324
392
  case method
325
393
  when GET_TREATMENT
326
394
  @telemetry_evaluation_producer.record_latency(Telemetry::Domain::Constants::TREATMENT, bucket)
@@ -330,9 +398,17 @@ module SplitIoClient
330
398
  @telemetry_evaluation_producer.record_latency(Telemetry::Domain::Constants::TREATMENT_WITH_CONFIG, bucket)
331
399
  when GET_TREATMENTS_WITH_CONFIG
332
400
  @telemetry_evaluation_producer.record_latency(Telemetry::Domain::Constants::TREATMENTS_WITH_CONFIG, bucket)
401
+ when GET_TREATMENTS_BY_FLAG_SET
402
+ @telemetry_evaluation_producer.record_latency(Telemetry::Domain::Constants::TREATMENTS_BY_FLAG_SET, bucket)
403
+ when GET_TREATMENTS_BY_FLAG_SETS
404
+ @telemetry_evaluation_producer.record_latency(Telemetry::Domain::Constants::TREATMENTS_BY_FLAG_SETS, bucket)
405
+ when GET_TREATMENT_WITH_CONFIG
406
+ @telemetry_evaluation_producer.record_latency(Telemetry::Domain::Constants::TREATMENT_WITH_CONFIG, bucket)
407
+ when GET_TREATMENTS_WITH_CONFIG
408
+ @telemetry_evaluation_producer.record_latency(Telemetry::Domain::Constants::TREATMENTS_WITH_CONFIG, bucket)
333
409
  when TRACK
334
410
  @telemetry_evaluation_producer.record_latency(Telemetry::Domain::Constants::TRACK, bucket)
335
- end
411
+ end
336
412
  end
337
413
 
338
414
  def record_exception(method)
@@ -345,6 +421,14 @@ module SplitIoClient
345
421
  @telemetry_evaluation_producer.record_exception(Telemetry::Domain::Constants::TREATMENT_WITH_CONFIG)
346
422
  when GET_TREATMENTS_WITH_CONFIG
347
423
  @telemetry_evaluation_producer.record_exception(Telemetry::Domain::Constants::TREATMENTS_WITH_CONFIG)
424
+ when GET_TREATMENTS_BY_FLAG_SET
425
+ @telemetry_evaluation_producer.record_exception(Telemetry::Domain::Constants::TREATMENTS_BY_FLAG_SET)
426
+ when GET_TREATMENTS_BY_FLAG_SETS
427
+ @telemetry_evaluation_producer.record_exception(Telemetry::Domain::Constants::TREATMENTS_BY_FLAG_SETS)
428
+ when GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SET
429
+ @telemetry_evaluation_producer.record_exception(Telemetry::Domain::Constants::TREATMENTS_WITH_CONFIG_BY_FLAG_SET)
430
+ when GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SETS
431
+ @telemetry_evaluation_producer.record_exception(Telemetry::Domain::Constants::TREATMENTS_WITH_CONFIG_BY_FLAG_SETS)
348
432
  when TRACK
349
433
  @telemetry_evaluation_producer.record_exception(Telemetry::Domain::Constants::TRACK)
350
434
  end
@@ -8,17 +8,22 @@ module SplitIoClient
8
8
  super(config)
9
9
  @api_key = api_key
10
10
  @telemetry_runtime_producer = telemetry_runtime_producer
11
+ @flag_sets_filter = @config.flag_sets_filter
11
12
  end
12
13
 
13
- def since(since, fetch_options = { cache_control_headers: false, till: nil })
14
+ def since(since, fetch_options = { cache_control_headers: false, till: nil, sets: nil })
14
15
  start = Time.now
15
-
16
+
16
17
  params = { since: since }
17
18
  params[:till] = fetch_options[:till] unless fetch_options[:till].nil?
19
+ params[:sets] = @flag_sets_filter.join(",") unless @flag_sets_filter.empty?
18
20
  response = get_api("#{@config.base_uri}/splitChanges", @api_key, params, fetch_options[:cache_control_headers])
21
+ if response.status == 414
22
+ @config.logger.error("Error fetching feature flags; the amount of flag sets provided are too big, causing uri length error.")
23
+ raise ApiException.new response.body, 414
24
+ end
19
25
  if response.success?
20
26
  result = splits_with_segment_names(response.body)
21
-
22
27
  unless result[:splits].empty?
23
28
  @config.split_logger.log_if_debug("#{result[:splits].length} feature flags retrieved. since=#{since}")
24
29
  end
@@ -12,7 +12,7 @@ module SplitIoClient
12
12
 
13
13
  def match?(args)
14
14
  keys = { matching_key: args[:matching_key], bucketing_key: args[:bucketing_key] }
15
- evaluate = args[:evaluator].call(keys, @feature_flag, args[:attributes])
15
+ evaluate = args[:evaluator].evaluate_feature_flag(keys, @feature_flag, args[:attributes])
16
16
  matches = @treatments.include?(evaluate[:treatment])
17
17
  @logger.log_if_debug("[dependencyMatcher] Parent feature flag #{@feature_flag} evaluated to #{evaluate[:treatment]} \
18
18
  with label #{evaluate[:label]}. #{@feature_flag} evaluated treatment is part of [#{@treatments}] ? #{matches}.")
@@ -2,41 +2,35 @@ module SplitIoClient
2
2
  module Engine
3
3
  module Parser
4
4
  class Evaluator
5
- def initialize(segments_repository, splits_repository, config, multiple = false)
5
+ def initialize(segments_repository, splits_repository, config)
6
6
  @splits_repository = splits_repository
7
7
  @segments_repository = segments_repository
8
- @multiple = multiple
9
8
  @config = config
10
- @cache = {}
11
9
  end
12
10
 
13
- def call(keys, split, attributes = nil)
11
+ def evaluate_feature_flag(keys, feature_flag, attributes = nil)
14
12
  # DependencyMatcher here, split is actually a split_name in this case
15
- cache_result = split.is_a? String
16
- split = @splits_repository.get_split(split) if cache_result
17
- digest = Digest::MD5.hexdigest("#{{matching_key: keys[:matching_key]}}#{split}#{attributes}")
13
+ cache_result = feature_flag.is_a? String
14
+ feature_flag = @splits_repository.get_split(feature_flag) if cache_result
15
+ evaluate_treatment(keys, feature_flag, attributes)
16
+ end
18
17
 
19
- if Models::Split.archived?(split)
20
- return treatment_hash(Models::Label::ARCHIVED, Models::Treatment::CONTROL, split[:changeNumber])
21
- end
18
+ private
22
19
 
23
- treatment = if Models::Split.matchable?(split)
24
- if @multiple && @cache[digest]
25
- @cache[digest]
26
- else
27
- match(split, keys, attributes)
28
- end
29
- else
30
- treatment_hash(Models::Label::KILLED, split[:defaultTreatment], split[:changeNumber], split_configurations(split[:defaultTreatment], split))
20
+ def evaluate_treatment(keys, feature_flag, attributes)
21
+ if Models::Split.archived?(feature_flag)
22
+ return treatment_hash(Models::Label::ARCHIVED, Models::Treatment::CONTROL, feature_flag[:changeNumber])
31
23
  end
32
24
 
33
- @cache[digest] = treatment if cache_result
25
+ treatment = if Models::Split.matchable?(feature_flag)
26
+ match(feature_flag, keys, attributes)
27
+ else
28
+ treatment_hash(Models::Label::KILLED, feature_flag[:defaultTreatment], feature_flag[:changeNumber], split_configurations(feature_flag[:defaultTreatment], feature_flag))
29
+ end
34
30
 
35
31
  treatment
36
32
  end
37
33
 
38
- private
39
-
40
34
  def split_configurations(treatment, split)
41
35
  return nil if split[:configurations].nil?
42
36
  split[:configurations][treatment.to_sym]
@@ -12,4 +12,15 @@ module SplitIoClient
12
12
  @event = event
13
13
  end
14
14
  end
15
+
16
+ class ApiException < SplitIoError
17
+ def initialize(msg, exception_code)
18
+ @@exception_code = exception_code
19
+ super(msg)
20
+ end
21
+ def exception_code
22
+ @@exception_code
23
+ end
24
+ end
25
+
15
26
  end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SplitIoClient
4
+ module Helpers
5
+ class RepositoryHelper
6
+ def self.update_feature_flag_repository(feature_flag_repository, feature_flags, change_number, config)
7
+ to_add = []
8
+ to_delete = []
9
+ feature_flags.each do |feature_flag|
10
+ if Engine::Models::Split.archived?(feature_flag) || !feature_flag_repository.flag_set_filter.intersect?(feature_flag[:sets])
11
+ config.logger.debug("removing feature flag from store(#{feature_flag})") if config.debug_enabled
12
+ to_delete.push(feature_flag)
13
+ next
14
+ end
15
+
16
+ config.logger.debug("storing feature flag (#{feature_flag[:name]})") if config.debug_enabled
17
+ to_add.push(feature_flag)
18
+ end
19
+ feature_flag_repository.update(to_add, to_delete, change_number)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -122,6 +122,7 @@ module SplitIoClient
122
122
  @on_demand_fetch_retry_delay_seconds = SplitConfig.default_on_demand_fetch_retry_delay_seconds
123
123
  @on_demand_fetch_max_retries = SplitConfig.default_on_demand_fetch_max_retries
124
124
 
125
+ @flag_sets_filter = SplitConfig.sanitize_flag_set_filter(opts[:flag_sets_filter], @split_validator, opts[:cache_adapter], @logger)
125
126
  startup_log
126
127
  end
127
128
 
@@ -287,7 +288,7 @@ module SplitIoClient
287
288
 
288
289
  attr_accessor :sdk_start_time
289
290
 
290
- attr_accessor :on_demand_fetch_retry_delay_seconds
291
+ attr_accessor :on_demand_fetch_retry_delay_seconds
291
292
  attr_accessor :on_demand_fetch_max_retries
292
293
 
293
294
  attr_accessor :unique_keys_refresh_rate
@@ -296,6 +297,12 @@ module SplitIoClient
296
297
 
297
298
  attr_accessor :counter_refresh_rate
298
299
 
300
+ #
301
+ # Flagsets filter
302
+ #
303
+ # @return [Array]
304
+ attr_accessor :flag_sets_filter
305
+
299
306
  def self.default_counter_refresh_rate(adapter)
300
307
  return 300 if adapter == :redis # Send bulk impressions count - Refresh rate: 5 min.
301
308
 
@@ -325,9 +332,9 @@ module SplitIoClient
325
332
  end
326
333
  end
327
334
 
328
- def self.init_impressions_refresh_rate(impressions_mode, refresh_rate, default_rate)
335
+ def self.init_impressions_refresh_rate(impressions_mode, refresh_rate, default_rate)
329
336
  return (refresh_rate.nil? || refresh_rate <= 0 ? default_rate : refresh_rate) if impressions_mode == :debug
330
-
337
+
331
338
  return refresh_rate.nil? || refresh_rate <= 0 ? SplitConfig.default_impressions_refresh_rate_optimized : [default_rate, refresh_rate].max
332
339
  end
333
340
 
@@ -482,7 +489,7 @@ module SplitIoClient
482
489
  end
483
490
 
484
491
  def self.default_telemetry_refresh_rate
485
- 3600
492
+ 3600
486
493
  end
487
494
 
488
495
  def self.default_unique_keys_refresh_rate(adapter)
@@ -513,6 +520,15 @@ module SplitIoClient
513
520
  5
514
521
  end
515
522
 
523
+ def self.sanitize_flag_set_filter(flag_sets, validator, adapter, logger)
524
+ return [] if flag_sets.nil?
525
+ if adapter == :redis
526
+ logger.warn("config: : flag_sets_filter is not applicable for Consumer modes where the SDK does not keep rollout data in sync. FlagSet filter was discarded")
527
+ return []
528
+ end
529
+ return validator.valid_flag_sets(:config, flag_sets)
530
+ end
531
+
516
532
  #
517
533
  # The default logger object
518
534
  #
@@ -646,7 +662,7 @@ module SplitIoClient
646
662
  return 'NA'.freeze
647
663
  end
648
664
  end
649
-
665
+
650
666
  return ''.freeze
651
667
  end
652
668
 
@@ -656,7 +672,7 @@ module SplitIoClient
656
672
  # @return [string]
657
673
  def self.machine_ip(ip_addresses_enabled, ip, adapter)
658
674
  if ip_addresses_enabled
659
- begin
675
+ begin
660
676
  return ip unless ip.nil? || ip.to_s.empty?
661
677
 
662
678
  loopback_ip = Socket.ip_address_list.find { |ip| ip.ipv4_loopback? }