statsig 1.34.1 → 2.0.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.
- checksums.yaml +4 -4
- data/lib/api_config.rb +0 -141
- data/lib/client_initialize_helpers.rb +26 -27
- 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
|
|