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.
- checksums.yaml +4 -4
- data/.github/CODEOWNERS +1 -0
- data/CHANGES.txt +3 -0
- data/lib/splitclient-rb/cache/fetchers/split_fetcher.rb +2 -29
- data/lib/splitclient-rb/cache/filter/flag_set_filter.rb +40 -0
- data/lib/splitclient-rb/cache/repositories/flag_sets/memory_repository.rb +40 -0
- data/lib/splitclient-rb/cache/repositories/flag_sets/redis_repository.rb +49 -0
- data/lib/splitclient-rb/cache/repositories/splits_repository.rb +100 -39
- data/lib/splitclient-rb/cache/stores/localhost_split_store.rb +1 -1
- data/lib/splitclient-rb/clients/split_client.rb +165 -81
- data/lib/splitclient-rb/engine/api/splits.rb +8 -3
- data/lib/splitclient-rb/engine/matchers/dependency_matcher.rb +1 -1
- data/lib/splitclient-rb/engine/parser/evaluator.rb +15 -21
- data/lib/splitclient-rb/exceptions.rb +11 -0
- data/lib/splitclient-rb/helpers/repository_helper.rb +23 -0
- data/lib/splitclient-rb/split_config.rb +22 -6
- data/lib/splitclient-rb/split_factory.rb +32 -9
- data/lib/splitclient-rb/sse/workers/splits_worker.rb +2 -9
- data/lib/splitclient-rb/telemetry/domain/constants.rb +4 -0
- data/lib/splitclient-rb/telemetry/domain/structs.rb +4 -4
- data/lib/splitclient-rb/telemetry/memory/memory_synchronizer.rb +18 -2
- data/lib/splitclient-rb/telemetry/redis/redis_synchronizer.rb +0 -1
- data/lib/splitclient-rb/telemetry/storages/memory.rb +12 -0
- data/lib/splitclient-rb/telemetry/synchronizer.rb +6 -2
- data/lib/splitclient-rb/validators.rb +64 -3
- data/lib/splitclient-rb/version.rb +1 -1
- data/lib/splitclient-rb.rb +4 -0
- 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
|
-
|
35
|
-
result
|
36
|
-
|
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
|
-
|
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(
|
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
|
-
|
140
|
-
|
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
|
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,
|
198
|
-
|
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
|
-
|
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
|
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
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
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
|
-
|
220
|
-
|
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
|
-
|
223
|
-
|
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:
|
226
|
-
config:
|
288
|
+
treatment: treatments_labels_change_numbers[:treatment],
|
289
|
+
config: treatments_labels_change_numbers[:config]
|
227
290
|
}
|
228
291
|
end
|
229
|
-
|
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
|
-
|
246
|
-
|
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(
|
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
|
-
|
318
|
+
sanitized_feature_flag_name = feature_flag_name.to_s.strip
|
259
319
|
|
260
|
-
if
|
261
|
-
@config.logger.warn("#{calling_method}: feature_flag_name #{
|
262
|
-
|
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
|
-
|
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
|
-
|
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 !
|
281
|
-
evaluator.
|
282
|
-
{ bucketing_key: bucketing_key, matching_key: matching_key },
|
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)
|
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,
|
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(
|
362
|
+
return parsed_treatment(control_treatment.merge({ label: Engine::Models::Label::EXCEPTION }), multiple), impressions
|
303
363
|
end
|
304
364
|
|
305
|
-
parsed_treatment(multiple,
|
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].
|
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
|
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
|
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 =
|
16
|
-
|
17
|
-
|
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
|
-
|
20
|
-
return treatment_hash(Models::Label::ARCHIVED, Models::Treatment::CONTROL, split[:changeNumber])
|
21
|
-
end
|
18
|
+
private
|
22
19
|
|
23
|
-
|
24
|
-
|
25
|
-
|
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
|
-
|
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? }
|