statsig 2.0.1 → 2.8.1
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 +1 -1
- data/lib/client_initialize_helpers.rb +30 -21
- data/lib/config_result.rb +21 -2
- data/lib/constants.rb +9 -0
- data/lib/dynamic_config.rb +14 -2
- data/lib/error_boundary.rb +1 -1
- data/lib/evaluation_helpers.rb +13 -1
- data/lib/evaluator.rb +368 -18
- data/lib/hash_utils.rb +48 -3
- data/lib/layer.rb +23 -4
- data/lib/memo.rb +5 -1
- data/lib/network.rb +10 -3
- data/lib/sdk_configs.rb +37 -0
- data/lib/spec_store.rb +97 -49
- data/lib/statsig.rb +59 -8
- data/lib/statsig_driver.rb +76 -28
- data/lib/statsig_event.rb +1 -2
- data/lib/statsig_logger.rb +137 -17
- data/lib/statsig_options.rb +7 -2
- data/lib/statsig_user.rb +1 -1
- data/lib/ttl_set.rb +36 -0
- data/lib/user_persistent_storage_utils.rb +27 -5
- metadata +9 -7
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 20ee1f5defc13286856fb81276a04db5e1078cc0b0c88223a4a361e71e8c2949
|
|
4
|
+
data.tar.gz: fd32bbe80668331c751091a7d9ddfad05c50eb1c2c37c037a50672731415af30
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c42d83f65d9689eb181d54eb23e703962550270fd82950c808f34d5e1ce45098ebaa1e89860cc35e0fff20c5efd528bf335cb06a897ef7b0486159c4a3a0af22
|
|
7
|
+
data.tar.gz: 0c41f82dc93999a11e9419cd008d7dcdeb74c42c2d11208ce3ed13168ec3e13a42d2fc4fd6314b5717a51ea1d57ba2c6880b010b4ae3fb131a59bbf2b9def55e
|
data/lib/api_config.rb
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
+
require_relative 'constants'
|
|
1
2
|
require_relative 'hash_utils'
|
|
2
3
|
|
|
3
|
-
require 'constants'
|
|
4
|
-
|
|
5
4
|
module Statsig
|
|
6
5
|
class ResponseFormatter
|
|
7
6
|
def self.get_responses(
|
|
@@ -31,7 +30,6 @@ module Statsig
|
|
|
31
30
|
end
|
|
32
31
|
|
|
33
32
|
def self.to_response(config_name, config_spec, evaluator, user, client_sdk_key, hash_algo, include_exposures, include_local_overrides)
|
|
34
|
-
config_name_str = config_name.to_s
|
|
35
33
|
category = config_spec[:type]
|
|
36
34
|
entity_type = config_spec[:entity]
|
|
37
35
|
if entity_type == Const::TYPE_SEGMENT || entity_type == Const::TYPE_HOLDOUT
|
|
@@ -47,11 +45,13 @@ module Statsig
|
|
|
47
45
|
end
|
|
48
46
|
end
|
|
49
47
|
|
|
48
|
+
config_name_str = config_name.to_s
|
|
50
49
|
if local_override.nil?
|
|
51
50
|
eval_result = ConfigResult.new(
|
|
52
51
|
name: config_name,
|
|
53
52
|
disable_evaluation_details: true,
|
|
54
|
-
disable_exposures: !include_exposures
|
|
53
|
+
disable_exposures: !include_exposures,
|
|
54
|
+
include_local_overrides: include_local_overrides
|
|
55
55
|
)
|
|
56
56
|
evaluator.eval_spec(config_name_str, user, config_spec, eval_result)
|
|
57
57
|
else
|
|
@@ -73,6 +73,7 @@ module Statsig
|
|
|
73
73
|
result[:value] = eval_result.json_value
|
|
74
74
|
result[:group] = eval_result.rule_id
|
|
75
75
|
result[:is_device_based] = id_type.is_a?(String) && id_type.downcase == Statsig::Const::STABLEID
|
|
76
|
+
result[:passed] = eval_result.gate_value
|
|
76
77
|
else
|
|
77
78
|
return nil
|
|
78
79
|
end
|
|
@@ -92,12 +93,26 @@ module Statsig
|
|
|
92
93
|
result[:rule_id] = eval_result.rule_id
|
|
93
94
|
|
|
94
95
|
if include_exposures
|
|
95
|
-
result[:secondary_exposures] = eval_result.secondary_exposures
|
|
96
|
+
result[:secondary_exposures] = hash_exposures(eval_result.secondary_exposures, hash_algo)
|
|
96
97
|
end
|
|
97
98
|
|
|
98
99
|
[hashed_name, result]
|
|
99
100
|
end
|
|
100
101
|
|
|
102
|
+
def self.hash_exposures(exposures, hash_algo)
|
|
103
|
+
return nil if exposures.nil?
|
|
104
|
+
hashed_exposures = []
|
|
105
|
+
exposures.each do |exp|
|
|
106
|
+
hashed_exposures << {
|
|
107
|
+
gate: hash_name(exp[:gate], hash_algo),
|
|
108
|
+
gateValue: exp[:gateValue],
|
|
109
|
+
ruleID: exp[:ruleID]
|
|
110
|
+
}
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
hashed_exposures
|
|
114
|
+
end
|
|
115
|
+
|
|
101
116
|
def self.populate_experiment_fields(config_name, config_spec, eval_result, result, evaluator)
|
|
102
117
|
result[:is_user_in_experiment] = eval_result.is_experiment_group
|
|
103
118
|
result[:is_experiment_active] = config_spec[:isActive] == true
|
|
@@ -108,14 +123,6 @@ module Statsig
|
|
|
108
123
|
|
|
109
124
|
result[:is_in_layer] = true
|
|
110
125
|
result[:explicit_parameters] = config_spec[:explicitParameters] || []
|
|
111
|
-
|
|
112
|
-
layer_name = evaluator.spec_store.experiment_to_layer[config_name]
|
|
113
|
-
if layer_name.nil? || evaluator.spec_store.layers[layer_name].nil?
|
|
114
|
-
return
|
|
115
|
-
end
|
|
116
|
-
|
|
117
|
-
layer = evaluator.spec_store.layers[layer_name]
|
|
118
|
-
result[:value] = layer[:defaultValue].merge(result[:value])
|
|
119
126
|
end
|
|
120
127
|
|
|
121
128
|
def self.populate_layer_fields(config_spec, eval_result, result, evaluator, hash_algo, include_exposures)
|
|
@@ -132,18 +139,20 @@ module Statsig
|
|
|
132
139
|
end
|
|
133
140
|
|
|
134
141
|
if include_exposures
|
|
135
|
-
result[:undelegated_secondary_exposures] = eval_result.undelegated_sec_exps || []
|
|
142
|
+
result[:undelegated_secondary_exposures] = hash_exposures(eval_result.undelegated_sec_exps || [], hash_algo)
|
|
136
143
|
end
|
|
137
144
|
end
|
|
138
145
|
|
|
139
146
|
def self.hash_name(name, hash_algo)
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
+
Statsig::Memo.for_global(:hash_name, "#{hash_algo}|#{name}") do
|
|
148
|
+
case hash_algo
|
|
149
|
+
when Statsig::Const::NONE
|
|
150
|
+
name
|
|
151
|
+
when Statsig::Const::DJB2
|
|
152
|
+
Statsig::HashUtils.djb2(name)
|
|
153
|
+
else
|
|
154
|
+
Statsig::HashUtils.sha256(name)
|
|
155
|
+
end
|
|
147
156
|
end
|
|
148
157
|
end
|
|
149
158
|
end
|
data/lib/config_result.rb
CHANGED
|
@@ -16,6 +16,12 @@ module Statsig
|
|
|
16
16
|
attr_accessor :target_app_ids
|
|
17
17
|
attr_accessor :disable_evaluation_details
|
|
18
18
|
attr_accessor :disable_exposures
|
|
19
|
+
attr_accessor :config_version
|
|
20
|
+
attr_accessor :include_local_overrides
|
|
21
|
+
attr_accessor :forward_all_exposures
|
|
22
|
+
attr_accessor :sampling_rate
|
|
23
|
+
attr_accessor :has_seen_analytical_gates
|
|
24
|
+
attr_accessor :override_config_name
|
|
19
25
|
|
|
20
26
|
def initialize(
|
|
21
27
|
name:,
|
|
@@ -31,7 +37,13 @@ module Statsig
|
|
|
31
37
|
id_type: nil,
|
|
32
38
|
target_app_ids: nil,
|
|
33
39
|
disable_evaluation_details: false,
|
|
34
|
-
disable_exposures: false
|
|
40
|
+
disable_exposures: false,
|
|
41
|
+
config_version: nil,
|
|
42
|
+
include_local_overrides: true,
|
|
43
|
+
forward_all_exposures: false,
|
|
44
|
+
sampling_rate: nil,
|
|
45
|
+
has_seen_analytical_gates: false,
|
|
46
|
+
override_config_name: nil
|
|
35
47
|
)
|
|
36
48
|
@name = name
|
|
37
49
|
@gate_value = gate_value
|
|
@@ -48,6 +60,12 @@ module Statsig
|
|
|
48
60
|
@target_app_ids = target_app_ids
|
|
49
61
|
@disable_evaluation_details = disable_evaluation_details
|
|
50
62
|
@disable_exposures = disable_exposures
|
|
63
|
+
@config_version = config_version
|
|
64
|
+
@include_local_overrides = include_local_overrides
|
|
65
|
+
@forward_all_exposures = forward_all_exposures
|
|
66
|
+
@sampling_rate = sampling_rate
|
|
67
|
+
@has_seen_analytical_gates = has_seen_analytical_gates
|
|
68
|
+
@override_config_name = override_config_name
|
|
51
69
|
end
|
|
52
70
|
|
|
53
71
|
def self.from_user_persisted_values(config_name, user_persisted_values)
|
|
@@ -67,7 +85,8 @@ module Statsig
|
|
|
67
85
|
init_time: @init_time,
|
|
68
86
|
group_name: @group_name,
|
|
69
87
|
id_type: @id_type,
|
|
70
|
-
target_app_ids: @target_app_ids
|
|
88
|
+
target_app_ids: @target_app_ids,
|
|
89
|
+
override_config_name: @override_config_name
|
|
71
90
|
}
|
|
72
91
|
end
|
|
73
92
|
end
|
data/lib/constants.rb
CHANGED
|
@@ -29,6 +29,8 @@ module Statsig
|
|
|
29
29
|
DISABLED = 'disabled'.freeze
|
|
30
30
|
DJB2 = 'djb2'.freeze
|
|
31
31
|
EMAIL = 'email'.freeze
|
|
32
|
+
EXPLORE = ':explore'.freeze
|
|
33
|
+
FAILS_TARGETING = 'inlineTargetingRules'.freeze
|
|
32
34
|
FALSE = 'false'.freeze
|
|
33
35
|
IP = 'ip'.freeze
|
|
34
36
|
LAYER = :layer
|
|
@@ -39,6 +41,7 @@ module Statsig
|
|
|
39
41
|
OSNAME = 'osname'.freeze
|
|
40
42
|
OSVERSION = 'osversion'.freeze
|
|
41
43
|
OVERRIDE = 'override'.freeze
|
|
44
|
+
PRESTART = 'prestart'.freeze
|
|
42
45
|
Q_RIGHT_CHEVRON = 'Q>'.freeze
|
|
43
46
|
STABLEID = 'stableid'.freeze
|
|
44
47
|
STATSIG_RUBY_SDK = 'statsig-ruby-sdk'.freeze
|
|
@@ -123,5 +126,11 @@ module Statsig
|
|
|
123
126
|
# API Operators (Segments)
|
|
124
127
|
OP_IN_SEGMENT_LIST = 'in_segment_list'.freeze
|
|
125
128
|
OP_NOT_IN_SEGMENT_LIST = 'not_in_segment_list'.freeze
|
|
129
|
+
|
|
130
|
+
# API Operators (Array)
|
|
131
|
+
OP_ARRAY_CONTAINS_ANY = 'array_contains_any'.freeze
|
|
132
|
+
OP_ARRAY_CONTAINS_NONE = 'array_contains_none'.freeze
|
|
133
|
+
OP_ARRAY_CONTAINS_ALL = 'array_contains_all'.freeze
|
|
134
|
+
OP_NOT_ARRAY_CONTAINS_ALL = 'not_array_contains_all'.freeze
|
|
126
135
|
end
|
|
127
136
|
end
|
data/lib/dynamic_config.rb
CHANGED
|
@@ -53,7 +53,19 @@ class DynamicConfig
|
|
|
53
53
|
index_sym = index.to_sym
|
|
54
54
|
return default_value unless @value.key?(index_sym)
|
|
55
55
|
|
|
56
|
-
|
|
57
|
-
|
|
56
|
+
value = @value[index_sym]
|
|
57
|
+
|
|
58
|
+
case default_value
|
|
59
|
+
when Integer
|
|
60
|
+
return value.to_i if value.is_a?(Numeric) && default_value.is_a?(Integer)
|
|
61
|
+
when Float
|
|
62
|
+
return value.to_f if value.is_a?(Numeric) && default_value.is_a?(Float)
|
|
63
|
+
when TrueClass, FalseClass
|
|
64
|
+
return value if [true, false].include?(value)
|
|
65
|
+
else
|
|
66
|
+
return value if value.class == default_value.class
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
default_value
|
|
58
70
|
end
|
|
59
71
|
end
|
data/lib/error_boundary.rb
CHANGED
data/lib/evaluation_helpers.rb
CHANGED
|
@@ -58,6 +58,18 @@ module EvaluationHelpers
|
|
|
58
58
|
end
|
|
59
59
|
end
|
|
60
60
|
|
|
61
|
+
def self.array_contains_any(value, target)
|
|
62
|
+
return false if value.nil? || target.nil?
|
|
63
|
+
value_set = value.to_set
|
|
64
|
+
return target.any? { |item| value_set.include?(item) || value_set.include?(item.to_i) }
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def self.array_contains_all(value, target)
|
|
68
|
+
return false if value.nil? || target.nil?
|
|
69
|
+
value_set = value.to_set
|
|
70
|
+
return target.all? { |item| value_set.include?(item) || value_set.include?(item.to_i) }
|
|
71
|
+
end
|
|
72
|
+
|
|
61
73
|
private
|
|
62
74
|
|
|
63
75
|
def self.is_numeric(v)
|
|
@@ -73,4 +85,4 @@ module EvaluationHelpers
|
|
|
73
85
|
end
|
|
74
86
|
return time.to_i
|
|
75
87
|
end
|
|
76
|
-
end
|
|
88
|
+
end
|