statsig 1.34.2 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/api_config.rb +0 -141
- data/lib/client_initialize_helpers.rb +21 -20
- data/lib/constants.rb +65 -0
- data/lib/dynamic_config.rb +13 -5
- data/lib/evaluation_helpers.rb +16 -5
- data/lib/evaluator.rb +146 -116
- data/lib/interfaces/data_store.rb +1 -1
- data/lib/layer.rb +12 -5
- data/lib/memo.rb +8 -6
- data/lib/network.rb +41 -28
- data/lib/spec_store.rb +39 -47
- data/lib/statsig.rb +1 -1
- data/lib/statsig_driver.rb +11 -6
- data/lib/statsig_options.rb +19 -10
- metadata +16 -3
- data/lib/uri_helper.rb +0 -29
data/lib/evaluator.rb
CHANGED
@@ -40,12 +40,13 @@ module Statsig
|
|
40
40
|
end
|
41
41
|
|
42
42
|
def lookup_gate_override(gate_name)
|
43
|
-
|
43
|
+
gate_name_sym = gate_name.to_sym
|
44
|
+
if @gate_overrides.key?(gate_name_sym)
|
44
45
|
return ConfigResult.new(
|
45
46
|
name: gate_name,
|
46
|
-
gate_value: @gate_overrides[
|
47
|
+
gate_value: @gate_overrides[gate_name_sym],
|
47
48
|
rule_id: Const::OVERRIDE,
|
48
|
-
id_type: @spec_store.has_gate?(gate_name) ? @spec_store.get_gate(gate_name)
|
49
|
+
id_type: @spec_store.has_gate?(gate_name) ? @spec_store.get_gate(gate_name)[:idType] : Const::EMPTY_STR,
|
49
50
|
evaluation_details: EvaluationDetails.local_override(
|
50
51
|
@spec_store.last_config_sync_time,
|
51
52
|
@spec_store.initial_config_sync_time
|
@@ -56,12 +57,13 @@ module Statsig
|
|
56
57
|
end
|
57
58
|
|
58
59
|
def lookup_config_override(config_name)
|
59
|
-
|
60
|
+
config_name_sym = config_name.to_sym
|
61
|
+
if @config_overrides.key?(config_name_sym)
|
60
62
|
return ConfigResult.new(
|
61
63
|
name: config_name,
|
62
|
-
json_value: @config_overrides[
|
64
|
+
json_value: @config_overrides[config_name_sym],
|
63
65
|
rule_id: Const::OVERRIDE,
|
64
|
-
id_type: @spec_store.has_config?(config_name) ? @spec_store.get_config(config_name)
|
66
|
+
id_type: @spec_store.has_config?(config_name) ? @spec_store.get_config(config_name)[:idType] : Const::EMPTY_STR,
|
65
67
|
evaluation_details: EvaluationDetails.local_override(
|
66
68
|
@spec_store.last_config_sync_time,
|
67
69
|
@spec_store.initial_config_sync_time
|
@@ -96,7 +98,7 @@ module Statsig
|
|
96
98
|
return
|
97
99
|
end
|
98
100
|
|
99
|
-
eval_spec(user, @spec_store.get_gate(gate_name), end_result, is_nested: is_nested)
|
101
|
+
eval_spec(gate_name, user, @spec_store.get_gate(gate_name), end_result, is_nested: is_nested)
|
100
102
|
end
|
101
103
|
|
102
104
|
def get_config(user, config_name, end_result, user_persisted_values: nil, ignore_local_overrides: false)
|
@@ -128,7 +130,7 @@ module Statsig
|
|
128
130
|
config = @spec_store.get_config(config_name)
|
129
131
|
|
130
132
|
# If persisted values is provided and the experiment is active, return sticky values if exists.
|
131
|
-
if !user_persisted_values.nil? && config
|
133
|
+
if !user_persisted_values.nil? && config[:isActive] == true
|
132
134
|
sticky_values = user_persisted_values[config_name]
|
133
135
|
unless sticky_values.nil?
|
134
136
|
end_result.gate_value = sticky_values[Statsig::Const::GATE_VALUE]
|
@@ -148,16 +150,16 @@ module Statsig
|
|
148
150
|
end
|
149
151
|
|
150
152
|
# If it doesn't exist, then save to persisted storage if the user was assigned to an experiment group.
|
151
|
-
eval_spec(user, config, end_result)
|
153
|
+
eval_spec(config_name, user, config, end_result)
|
152
154
|
if end_result.is_experiment_group
|
153
155
|
@persistent_storage_utils.add_evaluation_to_user_persisted_values(user_persisted_values, config_name,
|
154
156
|
end_result)
|
155
|
-
@persistent_storage_utils.save_to_storage(user, config
|
157
|
+
@persistent_storage_utils.save_to_storage(user, config[:idType], user_persisted_values)
|
156
158
|
end
|
157
159
|
# Otherwise, remove from persisted storage
|
158
160
|
else
|
159
|
-
@persistent_storage_utils.remove_experiment_from_storage(user, config
|
160
|
-
eval_spec(user, config, end_result)
|
161
|
+
@persistent_storage_utils.remove_experiment_from_storage(user, config[:idType], config_name)
|
162
|
+
eval_spec(config_name, user, config, end_result)
|
161
163
|
end
|
162
164
|
end
|
163
165
|
|
@@ -174,27 +176,44 @@ module Statsig
|
|
174
176
|
return
|
175
177
|
end
|
176
178
|
|
177
|
-
eval_spec(user, @spec_store.get_layer(layer_name), end_result)
|
179
|
+
eval_spec(layer_name, user, @spec_store.get_layer(layer_name), end_result)
|
178
180
|
end
|
179
181
|
|
180
182
|
def list_gates
|
181
|
-
@spec_store.gates.map
|
183
|
+
@spec_store.gates.keys.map(&:to_s)
|
182
184
|
end
|
183
185
|
|
184
186
|
def list_configs
|
185
|
-
|
187
|
+
keys = []
|
188
|
+
@spec_store.configs.each do |key, value|
|
189
|
+
if value[:entity] == Const::TYPE_DYNAMIC_CONFIG
|
190
|
+
keys << key.to_s
|
191
|
+
end
|
192
|
+
end
|
193
|
+
keys
|
186
194
|
end
|
187
195
|
|
188
196
|
def list_experiments
|
189
|
-
|
197
|
+
keys = []
|
198
|
+
@spec_store.configs.each do |key, value|
|
199
|
+
if value[:entity] == Const::TYPE_EXPERIMENT
|
200
|
+
keys << key.to_s
|
201
|
+
end
|
202
|
+
end
|
203
|
+
keys
|
190
204
|
end
|
191
205
|
|
192
206
|
def list_autotunes
|
193
|
-
|
194
|
-
|
207
|
+
keys = []
|
208
|
+
@spec_store.configs.each do |key, value|
|
209
|
+
if value[:entity] == Const::TYPE_AUTOTUNE
|
210
|
+
keys << key.to_s
|
211
|
+
end
|
212
|
+
end
|
213
|
+
keys end
|
195
214
|
|
196
215
|
def list_layers
|
197
|
-
@spec_store.layers.map
|
216
|
+
@spec_store.layers.keys.map(&:to_s)
|
198
217
|
end
|
199
218
|
|
200
219
|
def get_client_initialize_response(user, hash_algo, client_sdk_key, include_local_overrides)
|
@@ -258,11 +277,11 @@ module Statsig
|
|
258
277
|
end
|
259
278
|
|
260
279
|
def override_gate(gate, value)
|
261
|
-
@gate_overrides[gate] = value
|
280
|
+
@gate_overrides[gate.to_sym] = value
|
262
281
|
end
|
263
282
|
|
264
283
|
def remove_gate_override(gate)
|
265
|
-
@gate_overrides.delete(gate)
|
284
|
+
@gate_overrides.delete(gate.to_sym)
|
266
285
|
end
|
267
286
|
|
268
287
|
def clear_gate_overrides
|
@@ -270,33 +289,28 @@ module Statsig
|
|
270
289
|
end
|
271
290
|
|
272
291
|
def override_config(config, value)
|
273
|
-
@config_overrides[config] = value
|
292
|
+
@config_overrides[config.to_sym] = value
|
274
293
|
end
|
275
294
|
|
276
295
|
def remove_config_override(config)
|
277
|
-
@config_overrides.delete(config)
|
296
|
+
@config_overrides.delete(config.to_sym)
|
278
297
|
end
|
279
298
|
|
280
299
|
def clear_config_overrides
|
281
300
|
@config_overrides.clear
|
282
301
|
end
|
283
302
|
|
284
|
-
def eval_spec(user, config, end_result, is_nested: false)
|
285
|
-
|
286
|
-
finalize_eval_result(config, end_result, did_pass: false, rule: nil, is_nested: is_nested)
|
287
|
-
return
|
288
|
-
end
|
289
|
-
|
290
|
-
config.rules.each do |rule|
|
303
|
+
def eval_spec(config_name, user, config, end_result, is_nested: false)
|
304
|
+
config[:rules].each do |rule|
|
291
305
|
eval_rule(user, rule, end_result)
|
292
306
|
|
293
307
|
if end_result.gate_value
|
294
|
-
if eval_delegate(
|
308
|
+
if eval_delegate(config_name, user, rule, end_result)
|
295
309
|
finalize_secondary_exposures(end_result)
|
296
310
|
return
|
297
311
|
end
|
298
312
|
|
299
|
-
pass = eval_pass_percent(user, rule, config
|
313
|
+
pass = eval_pass_percent(user, rule, config[:salt])
|
300
314
|
finalize_eval_result(config, end_result, did_pass: pass, rule: rule, is_nested: is_nested)
|
301
315
|
return
|
302
316
|
end
|
@@ -308,20 +322,20 @@ module Statsig
|
|
308
322
|
private
|
309
323
|
|
310
324
|
def finalize_eval_result(config, end_result, did_pass:, rule:, is_nested: false)
|
311
|
-
end_result.id_type = config
|
312
|
-
end_result.target_app_ids = config
|
313
|
-
end_result.gate_value = did_pass
|
325
|
+
end_result.id_type = config[:idType]
|
326
|
+
end_result.target_app_ids = config[:targetAppIDs]
|
327
|
+
end_result.gate_value = did_pass ? rule[:returnValue] == true : config[:defaultValue] == true
|
314
328
|
|
315
329
|
if rule.nil?
|
316
|
-
end_result.json_value = config
|
330
|
+
end_result.json_value = config[:defaultValue]
|
317
331
|
end_result.group_name = nil
|
318
332
|
end_result.is_experiment_group = false
|
319
|
-
end_result.rule_id = config
|
333
|
+
end_result.rule_id = config[:enabled] ? Const::DEFAULT : Const::DISABLED
|
320
334
|
else
|
321
|
-
end_result.json_value = did_pass ? rule
|
322
|
-
end_result.group_name = rule
|
323
|
-
end_result.is_experiment_group = rule
|
324
|
-
end_result.rule_id = rule
|
335
|
+
end_result.json_value = did_pass ? rule[:returnValue] : config[:defaultValue]
|
336
|
+
end_result.group_name = rule[:groupName]
|
337
|
+
end_result.is_experiment_group = rule[:isExperimentGroup] == true
|
338
|
+
end_result.rule_id = rule[:id]
|
325
339
|
end
|
326
340
|
|
327
341
|
unless end_result.disable_evaluation_details
|
@@ -345,7 +359,7 @@ module Statsig
|
|
345
359
|
def clean_exposures(exposures)
|
346
360
|
seen = {}
|
347
361
|
exposures.reject do |exposure|
|
348
|
-
if exposure[:gate].to_s.start_with?(
|
362
|
+
if exposure[:gate].to_s.start_with?(Const::SEGMENT_PREFIX)
|
349
363
|
should_reject = true
|
350
364
|
else
|
351
365
|
key = "#{exposure[:gate]}|#{exposure[:gateValue]}|#{exposure[:ruleID]}}"
|
@@ -360,19 +374,10 @@ module Statsig
|
|
360
374
|
pass = true
|
361
375
|
i = 0
|
362
376
|
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
if condition.type == :fail_gate || condition.type == :pass_gate
|
369
|
-
result = eval_condition(user, condition, end_result)
|
370
|
-
else
|
371
|
-
result = Memo.for(memo, :eval_rule, condition.hash) do
|
372
|
-
eval_condition(user, condition, end_result)
|
373
|
-
end
|
374
|
-
end
|
375
|
-
|
377
|
+
until i >= rule[:conditions].length
|
378
|
+
condition_hash = rule[:conditions][i]
|
379
|
+
condition = @spec_store.get_condition(condition_hash)
|
380
|
+
result = eval_condition(user, condition, end_result)
|
376
381
|
pass = false if result != true
|
377
382
|
i += 1
|
378
383
|
end
|
@@ -381,82 +386,74 @@ module Statsig
|
|
381
386
|
end
|
382
387
|
|
383
388
|
def eval_delegate(name, user, rule, end_result)
|
384
|
-
return false unless (delegate = rule
|
385
|
-
return false unless (
|
389
|
+
return false unless (delegate = rule[:configDelegate])
|
390
|
+
return false unless (delegate_config = @spec_store.get_config(delegate))
|
386
391
|
|
387
392
|
end_result.undelegated_sec_exps = end_result.secondary_exposures.dup
|
388
393
|
|
389
|
-
eval_spec(user,
|
394
|
+
eval_spec(delegate, user, delegate_config, end_result, is_nested: true)
|
390
395
|
|
391
396
|
end_result.name = name
|
392
397
|
end_result.config_delegate = delegate
|
393
|
-
end_result.explicit_parameters =
|
398
|
+
end_result.explicit_parameters = delegate_config[:explicitParameters]
|
394
399
|
|
395
400
|
true
|
396
401
|
end
|
397
402
|
|
398
403
|
def eval_condition(user, condition, end_result)
|
399
404
|
value = nil
|
400
|
-
field = condition
|
401
|
-
target = condition
|
402
|
-
type = condition
|
403
|
-
operator = condition
|
404
|
-
additional_values = condition
|
405
|
-
id_type = condition
|
405
|
+
field = condition[:field]
|
406
|
+
target = condition[:targetValue]
|
407
|
+
type = condition[:type]
|
408
|
+
operator = condition[:operator]
|
409
|
+
additional_values = condition[:additionalValues]
|
410
|
+
id_type = condition[:idType]
|
406
411
|
|
407
412
|
case type
|
408
|
-
when
|
413
|
+
when Const::CND_PUBLIC
|
409
414
|
return true
|
410
|
-
when
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
gate: target,
|
417
|
-
gateValue: gate_value ? Const::TRUE : Const::FALSE,
|
418
|
-
ruleID: end_result.rule_id
|
419
|
-
}
|
420
|
-
end_result.secondary_exposures.append(new_exposure)
|
421
|
-
end
|
422
|
-
return type == :pass_gate ? gate_value : !gate_value
|
423
|
-
when :ip_based
|
415
|
+
when Const::CND_PASS_GATE, Const::CND_FAIL_GATE
|
416
|
+
result = eval_nested_gate(target, user, end_result)
|
417
|
+
return type == Const::CND_PASS_GATE ? result : !result
|
418
|
+
when Const::CND_MULTI_PASS_GATE, Const::CND_MULTI_FAIL_GATE
|
419
|
+
return eval_nested_gates(target, type, user, end_result)
|
420
|
+
when Const::CND_IP_BASED
|
424
421
|
value = get_value_from_user(user, field) || get_value_from_ip(user, field)
|
425
|
-
when
|
422
|
+
when Const::CND_UA_BASED
|
426
423
|
value = get_value_from_user(user, field) || get_value_from_ua(user, field)
|
427
|
-
when
|
424
|
+
when Const::CND_USER_FIELD
|
428
425
|
value = get_value_from_user(user, field)
|
429
|
-
when
|
426
|
+
when Const::CND_ENVIRONMENT_FIELD
|
430
427
|
value = get_value_from_environment(user, field)
|
431
|
-
when
|
428
|
+
when Const::CND_CURRENT_TIME
|
432
429
|
value = Time.now.to_i # epoch time in seconds
|
433
|
-
when
|
430
|
+
when Const::CND_USER_BUCKET
|
434
431
|
begin
|
435
432
|
salt = additional_values[:salt]
|
436
433
|
unit_id = user.get_unit_id(id_type) || Const::EMPTY_STR
|
437
434
|
# there are only 1000 user buckets as opposed to 10k for gate pass %
|
438
|
-
value = compute_user_hash("#{salt}.#{unit_id}") % 1000
|
435
|
+
value = (compute_user_hash("#{salt}.#{unit_id}") % 1000).to_s
|
439
436
|
rescue StandardError
|
440
437
|
return false
|
441
438
|
end
|
442
|
-
when
|
439
|
+
when Const::CND_UNIT_ID
|
443
440
|
value = user.get_unit_id(id_type)
|
444
441
|
end
|
445
442
|
|
446
443
|
case operator
|
447
444
|
# numerical comparison
|
448
|
-
when
|
445
|
+
when Const::OP_GREATER_THAN
|
449
446
|
return EvaluationHelpers.compare_numbers(value, target, ->(a, b) { a > b })
|
450
|
-
when
|
447
|
+
when Const::OP_GREATER_THAN_OR_EQUAL
|
451
448
|
return EvaluationHelpers.compare_numbers(value, target, ->(a, b) { a >= b })
|
452
|
-
when
|
449
|
+
when Const::OP_LESS_THAN
|
453
450
|
return EvaluationHelpers.compare_numbers(value, target, ->(a, b) { a < b })
|
454
|
-
when
|
451
|
+
when Const::OP_LESS_THAN_OR_EQUAL
|
455
452
|
return EvaluationHelpers.compare_numbers(value, target, ->(a, b) { a <= b })
|
456
453
|
|
457
454
|
# version comparison
|
458
455
|
# need to check for nil or empty value because Version takes them as valid values
|
459
|
-
when
|
456
|
+
when Const::OP_VERSION_GREATER_THAN
|
460
457
|
return false if value.to_s.empty?
|
461
458
|
|
462
459
|
return begin
|
@@ -464,7 +461,7 @@ module Statsig
|
|
464
461
|
rescue StandardError
|
465
462
|
false
|
466
463
|
end
|
467
|
-
when
|
464
|
+
when Const::OP_VERSION_GREATER_THAN_OR_EQUAL
|
468
465
|
return false if value.to_s.empty?
|
469
466
|
|
470
467
|
return begin
|
@@ -472,7 +469,7 @@ module Statsig
|
|
472
469
|
rescue StandardError
|
473
470
|
false
|
474
471
|
end
|
475
|
-
when
|
472
|
+
when Const::OP_VERSION_LESS_THAN
|
476
473
|
return false if value.to_s.empty?
|
477
474
|
|
478
475
|
return begin
|
@@ -480,7 +477,7 @@ module Statsig
|
|
480
477
|
rescue StandardError
|
481
478
|
false
|
482
479
|
end
|
483
|
-
when
|
480
|
+
when Const::OP_VERSION_LESS_THAN_OR_EQUAL
|
484
481
|
return false if value.to_s.empty?
|
485
482
|
|
486
483
|
return begin
|
@@ -488,7 +485,7 @@ module Statsig
|
|
488
485
|
rescue StandardError
|
489
486
|
false
|
490
487
|
end
|
491
|
-
when
|
488
|
+
when Const::OP_VERSION_EQUAL
|
492
489
|
return false if value.to_s.empty?
|
493
490
|
|
494
491
|
return begin
|
@@ -496,7 +493,7 @@ module Statsig
|
|
496
493
|
rescue StandardError
|
497
494
|
false
|
498
495
|
end
|
499
|
-
when
|
496
|
+
when Const::OP_VERSION_NOT_EQUAL
|
500
497
|
return false if value.to_s.empty?
|
501
498
|
|
502
499
|
return begin
|
@@ -506,45 +503,47 @@ module Statsig
|
|
506
503
|
end
|
507
504
|
|
508
505
|
# array operations
|
509
|
-
when
|
506
|
+
when Const::OP_ANY
|
510
507
|
return EvaluationHelpers::equal_string_in_array(target, value, true)
|
511
|
-
when
|
508
|
+
when Const::OP_NONE
|
512
509
|
return !EvaluationHelpers::equal_string_in_array(target, value, true)
|
513
|
-
when
|
510
|
+
when Const::OP_ANY_CASE_SENSITIVE
|
514
511
|
return EvaluationHelpers::equal_string_in_array(target, value, false)
|
515
|
-
when
|
512
|
+
when Const::OP_NONE_CASE_SENSITIVE
|
516
513
|
return !EvaluationHelpers::equal_string_in_array(target, value, false)
|
517
514
|
|
518
515
|
# string
|
519
|
-
when
|
516
|
+
when Const::OP_STR_STARTS_WITH_ANY
|
520
517
|
return EvaluationHelpers.match_string_in_array(target, value, true, ->(a, b) { a.start_with?(b) })
|
521
|
-
when
|
518
|
+
when Const::OP_STR_END_WITH_ANY
|
522
519
|
return EvaluationHelpers.match_string_in_array(target, value, true, ->(a, b) { a.end_with?(b) })
|
523
|
-
when
|
520
|
+
when Const::OP_STR_CONTAINS_ANY
|
524
521
|
return EvaluationHelpers.match_string_in_array(target, value, true, ->(a, b) { a.include?(b) })
|
525
|
-
when
|
522
|
+
when Const::OP_STR_CONTAINS_NONE
|
526
523
|
return !EvaluationHelpers.match_string_in_array(target, value, true, ->(a, b) { a.include?(b) })
|
527
|
-
when
|
524
|
+
when Const::OP_STR_MATCHES
|
528
525
|
return begin
|
529
526
|
value&.is_a?(String) && !(value =~ Regexp.new(target)).nil?
|
530
527
|
rescue StandardError
|
531
528
|
false
|
532
529
|
end
|
533
|
-
when
|
530
|
+
when Const::OP_EQUAL
|
534
531
|
return value == target
|
535
|
-
when
|
532
|
+
when Const::OP_NOT_EQUAL
|
536
533
|
return value != target
|
537
534
|
|
538
535
|
# dates
|
539
|
-
when
|
536
|
+
when Const::OP_BEFORE
|
540
537
|
return EvaluationHelpers.compare_times(value, target, ->(a, b) { a < b })
|
541
|
-
when
|
538
|
+
when Const::OP_AFTER
|
542
539
|
return EvaluationHelpers.compare_times(value, target, ->(a, b) { a > b })
|
543
|
-
when
|
540
|
+
when Const::OP_ON
|
544
541
|
return EvaluationHelpers.compare_times(value, target, lambda { |a, b|
|
545
542
|
a.year == b.year && a.month == b.month && a.day == b.day
|
546
543
|
})
|
547
|
-
|
544
|
+
|
545
|
+
# segments
|
546
|
+
when Const::OP_IN_SEGMENT_LIST, Const::OP_NOT_IN_SEGMENT_LIST
|
548
547
|
begin
|
549
548
|
is_in_list = false
|
550
549
|
id_list = @spec_store.get_id_list(target)
|
@@ -552,7 +551,7 @@ module Statsig
|
|
552
551
|
hashed_id = Digest::SHA256.base64digest(value.to_s)[0, 8]
|
553
552
|
is_in_list = id_list.ids.include?(hashed_id)
|
554
553
|
end
|
555
|
-
return is_in_list if operator ==
|
554
|
+
return is_in_list if operator == Const::OP_IN_SEGMENT_LIST
|
556
555
|
|
557
556
|
return !is_in_list
|
558
557
|
rescue StandardError
|
@@ -562,6 +561,37 @@ module Statsig
|
|
562
561
|
return false
|
563
562
|
end
|
564
563
|
|
564
|
+
def eval_nested_gate(gate_name, user, end_result)
|
565
|
+
check_gate(user, gate_name, end_result, is_nested: true)
|
566
|
+
gate_value = end_result.gate_value
|
567
|
+
|
568
|
+
unless end_result.disable_exposures
|
569
|
+
new_exposure = {
|
570
|
+
gate: gate_name,
|
571
|
+
gateValue: gate_value ? Const::TRUE : Const::FALSE,
|
572
|
+
ruleID: end_result.rule_id
|
573
|
+
}
|
574
|
+
end_result.secondary_exposures.append(new_exposure)
|
575
|
+
end
|
576
|
+
|
577
|
+
gate_value
|
578
|
+
end
|
579
|
+
|
580
|
+
def eval_nested_gates(gate_names, condition_type, user, end_result)
|
581
|
+
has_passing_gate = false
|
582
|
+
is_multi_pass_gate_type = condition_type == Const::CND_MULTI_PASS_GATE
|
583
|
+
gate_names.each { |gate_name|
|
584
|
+
result = eval_nested_gate(gate_name, user, end_result)
|
585
|
+
|
586
|
+
if is_multi_pass_gate_type == result
|
587
|
+
has_passing_gate = true
|
588
|
+
break
|
589
|
+
end
|
590
|
+
}
|
591
|
+
|
592
|
+
has_passing_gate
|
593
|
+
end
|
594
|
+
|
565
595
|
def get_value_from_user(user, field)
|
566
596
|
return nil unless field.is_a?(String)
|
567
597
|
|
@@ -640,10 +670,10 @@ module Statsig
|
|
640
670
|
end
|
641
671
|
|
642
672
|
def eval_pass_percent(user, rule, config_salt)
|
643
|
-
unit_id = user.get_unit_id(rule
|
644
|
-
rule_salt = rule
|
673
|
+
unit_id = user.get_unit_id(rule[:idType]) || Const::EMPTY_STR
|
674
|
+
rule_salt = rule[:salt] || rule[:id] || Const::EMPTY_STR
|
645
675
|
hash = compute_user_hash("#{config_salt}.#{rule_salt}.#{unit_id}")
|
646
|
-
return (hash % 10_000) < (rule
|
676
|
+
return (hash % 10_000) < (rule[:passPercentage] * 100)
|
647
677
|
end
|
648
678
|
|
649
679
|
def compute_user_hash(user_hash)
|
data/lib/layer.rb
CHANGED
@@ -28,13 +28,16 @@ class Layer
|
|
28
28
|
# @param index The name of parameter being fetched
|
29
29
|
# @param default_value The fallback value if the name cannot be found
|
30
30
|
def get(index, default_value)
|
31
|
-
return default_value if @value.nil?
|
31
|
+
return default_value if @value.nil?
|
32
|
+
|
33
|
+
index_sym = index.to_sym
|
34
|
+
return default_value unless @value.key?(index_sym)
|
32
35
|
|
33
36
|
if @exposure_log_func.is_a? Proc
|
34
37
|
@exposure_log_func.call(self, index)
|
35
38
|
end
|
36
39
|
|
37
|
-
@value[
|
40
|
+
@value[index_sym]
|
38
41
|
end
|
39
42
|
|
40
43
|
##
|
@@ -44,13 +47,17 @@ class Layer
|
|
44
47
|
# @param index The name of parameter being fetched
|
45
48
|
# @param default_value The fallback value if the name cannot be found
|
46
49
|
def get_typed(index, default_value)
|
47
|
-
return default_value if @value.nil?
|
48
|
-
|
50
|
+
return default_value if @value.nil?
|
51
|
+
|
52
|
+
index_sym = index.to_sym
|
53
|
+
return default_value unless @value.key?(index_sym)
|
54
|
+
|
55
|
+
return default_value if @value[index_sym].class != default_value.class and default_value.class != TrueClass and default_value.class != FalseClass
|
49
56
|
|
50
57
|
if @exposure_log_func.is_a? Proc
|
51
58
|
@exposure_log_func.call(self, index)
|
52
59
|
end
|
53
60
|
|
54
|
-
@value[
|
61
|
+
@value[index_sym]
|
55
62
|
end
|
56
63
|
end
|
data/lib/memo.rb
CHANGED
@@ -4,13 +4,15 @@ module Statsig
|
|
4
4
|
@global_memo = {}
|
5
5
|
|
6
6
|
def self.for(hash, method, key)
|
7
|
-
|
8
|
-
|
9
|
-
method_hash
|
7
|
+
if key != nil
|
8
|
+
method_hash = hash[method]
|
9
|
+
unless method_hash
|
10
|
+
method_hash = hash[method] = {}
|
11
|
+
end
|
12
|
+
|
13
|
+
return method_hash[key] if method_hash.key?(key)
|
10
14
|
end
|
11
|
-
|
12
|
-
return method_hash[key] if method_hash.key?(key)
|
13
|
-
|
15
|
+
|
14
16
|
method_hash[key] = yield
|
15
17
|
end
|
16
18
|
|