statsig 1.9.0 → 1.9.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/config_result.rb +5 -1
- data/lib/evaluator.rb +38 -25
- data/lib/id_list.rb +36 -0
- data/lib/layer.rb +7 -1
- data/lib/network.rb +3 -0
- data/lib/spec_store.rb +86 -39
- data/lib/statsig.rb +2 -2
- data/lib/statsig_driver.rb +6 -4
- data/lib/statsig_logger.rb +15 -5
- data/lib/statsig_options.rb +1 -1
- data/lib/statsig_user.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 72321b660214bc1a0c54ffd5718115ef713be11fd0f37fe9e18429539edad855
|
4
|
+
data.tar.gz: 3583760dd5c9ae9aab5005bc07e7673088de8af39b7cbecec032b332249b1c6a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bf06b989ebade0705ef7ee377455b87c6b408ce38aabfae9ccc6c4f95f6b7bc0a4e3fb9393d5473b0e32f2e04f901df730666f00a27b2f0d2a709338b31ca2bc
|
7
|
+
data.tar.gz: 68e77d69004f9d96d82e011ad67822732c9889871eb0e39990b88c9f109b4f84d991c55a680cd5434381c285e35e725d08e7438ee5fe1bc32809f0dd9eb78141
|
data/lib/config_result.rb
CHANGED
@@ -6,15 +6,19 @@ module Statsig
|
|
6
6
|
attr_accessor :json_value
|
7
7
|
attr_accessor :rule_id
|
8
8
|
attr_accessor :secondary_exposures
|
9
|
+
attr_accessor :undelegated_sec_exps
|
9
10
|
attr_accessor :config_delegate
|
11
|
+
attr_accessor :explicit_parameters
|
10
12
|
|
11
|
-
def initialize(name, gate_value = false, json_value = {}, rule_id = '', secondary_exposures = [], config_delegate = '')
|
13
|
+
def initialize(name, gate_value = false, json_value = {}, rule_id = '', secondary_exposures = [], undelegated_sec_exps = [], config_delegate = '', explicit_parameters = [])
|
12
14
|
@name = name
|
13
15
|
@gate_value = gate_value
|
14
16
|
@json_value = json_value
|
15
17
|
@rule_id = rule_id
|
16
18
|
@secondary_exposures = secondary_exposures.is_a?(Array) ? secondary_exposures : []
|
19
|
+
@undelegated_sec_exps = undelegated_sec_exps.is_a?(Array) ? undelegated_sec_exps : []
|
17
20
|
@config_delegate = config_delegate
|
21
|
+
@explicit_parameters = explicit_parameters
|
18
22
|
end
|
19
23
|
end
|
20
24
|
end
|
data/lib/evaluator.rb
CHANGED
@@ -7,7 +7,7 @@ require 'time'
|
|
7
7
|
require 'user_agent_parser'
|
8
8
|
require 'user_agent_parser/operating_system'
|
9
9
|
|
10
|
-
$fetch_from_server =
|
10
|
+
$fetch_from_server = 'fetch_from_server'
|
11
11
|
$type_dynamic_config = 'dynamic_config'
|
12
12
|
|
13
13
|
module Statsig
|
@@ -48,17 +48,21 @@ module Statsig
|
|
48
48
|
until i >= config['rules'].length do
|
49
49
|
rule = config['rules'][i]
|
50
50
|
result = eval_rule(user, rule)
|
51
|
-
return $fetch_from_server if result == $fetch_from_server
|
51
|
+
return $fetch_from_server if result.to_s == $fetch_from_server
|
52
52
|
exposures = exposures + result.secondary_exposures
|
53
53
|
if result.gate_value
|
54
|
+
|
55
|
+
if (delegated_result = eval_delegate(config['name'], user, rule, exposures))
|
56
|
+
return delegated_result
|
57
|
+
end
|
58
|
+
|
54
59
|
pass = eval_pass_percent(user, rule, config['salt'])
|
55
60
|
return Statsig::ConfigResult.new(
|
56
61
|
config['name'],
|
57
62
|
pass,
|
58
63
|
pass ? result.json_value : config['defaultValue'],
|
59
64
|
result.rule_id,
|
60
|
-
exposures
|
61
|
-
result.config_delegate
|
65
|
+
exposures
|
62
66
|
)
|
63
67
|
end
|
64
68
|
|
@@ -77,7 +81,7 @@ module Statsig
|
|
77
81
|
i = 0
|
78
82
|
until i >= rule['conditions'].length do
|
79
83
|
result = eval_condition(user, rule['conditions'][i])
|
80
|
-
if result == $fetch_from_server
|
84
|
+
if result.to_s == $fetch_from_server
|
81
85
|
return $fetch_from_server
|
82
86
|
end
|
83
87
|
|
@@ -90,17 +94,24 @@ module Statsig
|
|
90
94
|
i += 1
|
91
95
|
end
|
92
96
|
|
93
|
-
delegate = rule['configDelegate']
|
94
|
-
if pass and @spec_store.get_config(delegate)
|
95
|
-
delegated_result = self.eval_spec(user, @spec_store.get_config(delegate))
|
96
|
-
delegated_result.config_delegate = delegate
|
97
|
-
delegated_result.secondary_exposures = exposures + delegated_result.secondary_exposures
|
98
|
-
return delegated_result
|
99
|
-
end
|
100
|
-
|
101
97
|
Statsig::ConfigResult.new('', pass, rule['returnValue'], rule['id'], exposures)
|
102
98
|
end
|
103
99
|
|
100
|
+
def eval_delegate(name, user, rule, exposures)
|
101
|
+
return nil unless (delegate = rule['configDelegate'])
|
102
|
+
return nil unless (config = @spec_store.get_config(delegate))
|
103
|
+
|
104
|
+
delegated_result = self.eval_spec(user, config)
|
105
|
+
return $fetch_from_server if delegated_result.to_s == $fetch_from_server
|
106
|
+
|
107
|
+
delegated_result.name = name
|
108
|
+
delegated_result.config_delegate = delegate
|
109
|
+
delegated_result.secondary_exposures = exposures + delegated_result.secondary_exposures
|
110
|
+
delegated_result.undelegated_sec_exps = exposures
|
111
|
+
delegated_result.explicit_parameters = config['explicitParameters']
|
112
|
+
delegated_result
|
113
|
+
end
|
114
|
+
|
104
115
|
def eval_condition(user, condition)
|
105
116
|
value = nil
|
106
117
|
field = condition['field']
|
@@ -119,7 +130,7 @@ module Statsig
|
|
119
130
|
return true
|
120
131
|
when 'fail_gate', 'pass_gate'
|
121
132
|
other_gate_result = check_gate(user, target)
|
122
|
-
return $fetch_from_server if other_gate_result == $fetch_from_server
|
133
|
+
return $fetch_from_server if other_gate_result.to_s == $fetch_from_server
|
123
134
|
|
124
135
|
gate_value = other_gate_result&.gate_value == true
|
125
136
|
new_exposure = {
|
@@ -134,10 +145,10 @@ module Statsig
|
|
134
145
|
}
|
135
146
|
when 'ip_based'
|
136
147
|
value = get_value_from_user(user, field) || get_value_from_ip(user, field)
|
137
|
-
return $fetch_from_server if value == $fetch_from_server
|
148
|
+
return $fetch_from_server if value.to_s == $fetch_from_server
|
138
149
|
when 'ua_based'
|
139
150
|
value = get_value_from_user(user, field) || get_value_from_ua(user, field)
|
140
|
-
return $fetch_from_server if value == $fetch_from_server
|
151
|
+
return $fetch_from_server if value.to_s == $fetch_from_server
|
141
152
|
when 'user_field'
|
142
153
|
value = get_value_from_user(user, field)
|
143
154
|
when 'environment_field'
|
@@ -159,7 +170,7 @@ module Statsig
|
|
159
170
|
return $fetch_from_server
|
160
171
|
end
|
161
172
|
|
162
|
-
return $fetch_from_server if value == $fetch_from_server || !operator.is_a?(String)
|
173
|
+
return $fetch_from_server if value.to_s == $fetch_from_server || !operator.is_a?(String)
|
163
174
|
operator = operator.downcase
|
164
175
|
|
165
176
|
case operator
|
@@ -229,13 +240,15 @@ module Statsig
|
|
229
240
|
return EvaluationHelpers::compare_times(value, target, ->(a, b) { a.year == b.year && a.month == b.month && a.day == b.day })
|
230
241
|
when 'in_segment_list', 'not_in_segment_list'
|
231
242
|
begin
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
243
|
+
is_in_list = false
|
244
|
+
id_list = @spec_store.get_id_list(target)
|
245
|
+
if id_list.is_a? IDList
|
246
|
+
hashed_id = Digest::SHA256.base64digest(value.to_s)[0, 8]
|
247
|
+
is_in_list = id_list.ids.include?(hashed_id)
|
248
|
+
end
|
249
|
+
return is_in_list if operator == 'in_segment_list'
|
250
|
+
return !is_in_list
|
251
|
+
rescue
|
239
252
|
return false
|
240
253
|
end
|
241
254
|
else
|
@@ -309,7 +322,7 @@ module Statsig
|
|
309
322
|
def eval_pass_percent(user, rule, config_salt)
|
310
323
|
return false unless config_salt.is_a?(String) && !rule['passPercentage'].nil?
|
311
324
|
begin
|
312
|
-
unit_id = get_unit_id(user, rule['
|
325
|
+
unit_id = get_unit_id(user, rule['idType']) || ''
|
313
326
|
rule_salt = rule['salt'] || rule['id'] || ''
|
314
327
|
hash = compute_user_hash("#{config_salt}.#{rule_salt}.#{unit_id}")
|
315
328
|
return (hash % 10000) < (rule['passPercentage'].to_f * 100)
|
data/lib/id_list.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
module Statsig
|
2
|
+
class IDList
|
3
|
+
attr_accessor :name
|
4
|
+
attr_accessor :size
|
5
|
+
attr_accessor :creation_time
|
6
|
+
attr_accessor :url
|
7
|
+
attr_accessor :file_id
|
8
|
+
attr_accessor :ids
|
9
|
+
|
10
|
+
def initialize(json, ids = Set.new)
|
11
|
+
@name = json['name'] || ''
|
12
|
+
@size = json['size'] || 0
|
13
|
+
@creation_time = json['creationTime'] || 0
|
14
|
+
@url = json['url']
|
15
|
+
@file_id = json['fileID']
|
16
|
+
|
17
|
+
@ids = ids
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.new_empty(json)
|
21
|
+
self.new(json)
|
22
|
+
@size = 0
|
23
|
+
end
|
24
|
+
|
25
|
+
def ==(other)
|
26
|
+
return false if other.nil?
|
27
|
+
|
28
|
+
self.name == other.name &&
|
29
|
+
self.size == other.size &&
|
30
|
+
self.creation_time == other.creation_time &&
|
31
|
+
self.url == other.url &&
|
32
|
+
self.file_id == other.file_id &&
|
33
|
+
self.ids == other.ids
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
data/lib/layer.rb
CHANGED
@@ -2,14 +2,20 @@ class Layer
|
|
2
2
|
attr_accessor :name
|
3
3
|
attr_accessor :rule_id
|
4
4
|
|
5
|
-
def initialize(name, value = {}, rule_id = '')
|
5
|
+
def initialize(name, value = {}, rule_id = '', exposure_log_func = nil)
|
6
6
|
@name = name
|
7
7
|
@value = value
|
8
8
|
@rule_id = rule_id
|
9
|
+
@exposure_log_func = exposure_log_func
|
9
10
|
end
|
10
11
|
|
11
12
|
def get(index, default_value)
|
12
13
|
return default_value if @value.nil? || !@value.key?(index)
|
14
|
+
|
15
|
+
if @exposure_log_func.is_a? Proc
|
16
|
+
@exposure_log_func.call(self, index)
|
17
|
+
end
|
18
|
+
|
13
19
|
@value[index]
|
14
20
|
end
|
15
21
|
end
|
data/lib/network.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'http'
|
2
2
|
require 'json'
|
3
|
+
require 'securerandom'
|
3
4
|
|
4
5
|
$retry_codes = [408, 500, 502, 503, 504, 522, 524, 599]
|
5
6
|
|
@@ -13,12 +14,14 @@ module Statsig
|
|
13
14
|
@server_secret = server_secret
|
14
15
|
@api = api
|
15
16
|
@backoff_multiplier = backoff_mult
|
17
|
+
@session_id = SecureRandom.uuid
|
16
18
|
end
|
17
19
|
|
18
20
|
def post_helper(endpoint, body, retries = 0, backoff = 1)
|
19
21
|
http = HTTP.headers(
|
20
22
|
{"STATSIG-API-KEY" => @server_secret,
|
21
23
|
"STATSIG-CLIENT-TIME" => (Time.now.to_f * 1000).to_s,
|
24
|
+
"STATSIG-SERVER-SESSION-ID" => @session_id,
|
22
25
|
"Content-Type" => "application/json; charset=UTF-8"
|
23
26
|
}).accept(:json)
|
24
27
|
begin
|
data/lib/spec_store.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
require 'net/http'
|
2
2
|
require 'uri'
|
3
3
|
|
4
|
+
require 'id_list'
|
5
|
+
|
4
6
|
module Statsig
|
5
7
|
class SpecStore
|
6
8
|
def initialize(network, error_callback = nil, config_sync_interval = 10, id_lists_sync_interval = 60)
|
@@ -16,7 +18,7 @@ module Statsig
|
|
16
18
|
}
|
17
19
|
e = download_config_specs
|
18
20
|
error_callback.call(e) unless error_callback.nil?
|
19
|
-
|
21
|
+
get_id_lists
|
20
22
|
|
21
23
|
@config_sync_thread = sync_config_specs
|
22
24
|
@id_lists_sync_thread = sync_id_lists
|
@@ -73,7 +75,7 @@ module Statsig
|
|
73
75
|
Thread.new do
|
74
76
|
loop do
|
75
77
|
sleep @id_lists_sync_interval
|
76
|
-
|
78
|
+
get_id_lists
|
77
79
|
end
|
78
80
|
end
|
79
81
|
end
|
@@ -112,53 +114,98 @@ module Statsig
|
|
112
114
|
@store[:gates] = new_gates
|
113
115
|
@store[:configs] = new_configs
|
114
116
|
@store[:layers] = new_layers
|
117
|
+
end
|
115
118
|
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
119
|
+
def get_id_lists
|
120
|
+
response, e = @network.post_helper('get_id_lists', JSON.generate({'statsigMetadata' => Statsig.get_statsig_metadata}))
|
121
|
+
if !e.nil? || response.nil?
|
122
|
+
return
|
123
|
+
end
|
124
|
+
|
125
|
+
begin
|
126
|
+
server_id_lists = JSON.parse(response)
|
127
|
+
local_id_lists = @store[:id_lists]
|
128
|
+
if !server_id_lists.is_a?(Hash) || !local_id_lists.is_a?(Hash)
|
129
|
+
return
|
122
130
|
end
|
131
|
+
threads = []
|
132
|
+
|
133
|
+
server_id_lists.each do |list_name, list|
|
134
|
+
server_list = IDList.new(list)
|
135
|
+
local_list = get_id_list(list_name)
|
136
|
+
|
137
|
+
unless local_list.is_a? IDList
|
138
|
+
local_list = IDList.new(list)
|
139
|
+
local_list.size = 0
|
140
|
+
local_id_lists[list_name] = local_list
|
141
|
+
end
|
123
142
|
|
124
|
-
|
125
|
-
|
126
|
-
|
143
|
+
# skip if server list is invalid
|
144
|
+
if server_list.url.nil? || server_list.creation_time < local_list.creation_time || server_list.file_id.nil?
|
145
|
+
next
|
146
|
+
end
|
147
|
+
|
148
|
+
# skip if server list returns a newer file
|
149
|
+
if server_list.file_id != local_list.file_id && server_list.creation_time >= local_list.creation_time
|
150
|
+
local_list = IDList.new(list)
|
151
|
+
local_list.size = 0
|
152
|
+
local_id_lists[list_name] = local_list
|
153
|
+
end
|
154
|
+
|
155
|
+
# skip if server list is no bigger than local list, which means nothing new to read
|
156
|
+
if server_list.size <= local_list.size
|
157
|
+
next
|
158
|
+
end
|
159
|
+
|
160
|
+
threads << Thread.new do
|
161
|
+
download_single_id_list(local_list)
|
162
|
+
end
|
163
|
+
end
|
164
|
+
threads.each(&:join)
|
165
|
+
delete_lists = []
|
166
|
+
local_id_lists.each do |list_name, list|
|
167
|
+
unless server_id_lists.key? list_name
|
168
|
+
delete_lists.push list_name
|
127
169
|
end
|
128
170
|
end
|
171
|
+
delete_lists.each do |list_name|
|
172
|
+
local_id_lists.delete list_name
|
173
|
+
end
|
174
|
+
rescue
|
175
|
+
# Ignored, will try again
|
129
176
|
end
|
130
177
|
end
|
131
178
|
|
132
|
-
def
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
# Ignored
|
157
|
-
end
|
158
|
-
end
|
179
|
+
def download_single_id_list(list)
|
180
|
+
nil unless list.is_a? IDList
|
181
|
+
http = HTTP.headers({'Range' => "bytes=#{list&.size || 0}-"}).accept(:json)
|
182
|
+
begin
|
183
|
+
res = http.get(list.url)
|
184
|
+
nil unless res.status.success?
|
185
|
+
content_length = Integer(res['content-length'])
|
186
|
+
nil if content_length.nil? || content_length <= 0
|
187
|
+
content = res.body.to_s
|
188
|
+
unless content.is_a?(String) && (content[0] == '-' || content[0] == '+')
|
189
|
+
@store[:id_lists].delete(list.name)
|
190
|
+
return
|
191
|
+
end
|
192
|
+
ids_clone = list.ids # clone the list, operate on the new list, and swap out the old list, so the operation is thread-safe
|
193
|
+
lines = content.split(/\r?\n/)
|
194
|
+
lines.each do |li|
|
195
|
+
line = li.strip
|
196
|
+
next if line.length <= 1
|
197
|
+
op = line[0]
|
198
|
+
id = line[1..]
|
199
|
+
if op == '+'
|
200
|
+
ids_clone.add(id)
|
201
|
+
elsif op == '-'
|
202
|
+
ids_clone.delete(id)
|
159
203
|
end
|
160
204
|
end
|
161
|
-
|
205
|
+
list.ids = ids_clone
|
206
|
+
list.size = list.size + content_length
|
207
|
+
rescue
|
208
|
+
nil
|
162
209
|
end
|
163
210
|
end
|
164
211
|
end
|
data/lib/statsig.rb
CHANGED
@@ -22,12 +22,12 @@ module Statsig
|
|
22
22
|
|
23
23
|
def self.get_experiment(user, experiment_name)
|
24
24
|
ensure_initialized
|
25
|
-
@shared_instance&.
|
25
|
+
@shared_instance&.get_experiment(user, experiment_name)
|
26
26
|
end
|
27
27
|
|
28
28
|
def self.get_layer(user, layer_name)
|
29
29
|
ensure_initialized
|
30
|
-
@shared_instance&.
|
30
|
+
@shared_instance&.get_layer(user, layer_name)
|
31
31
|
end
|
32
32
|
|
33
33
|
def self.log_event(user, event_name, value, metadata)
|
data/lib/statsig_driver.rb
CHANGED
@@ -6,6 +6,8 @@ require 'statsig_logger'
|
|
6
6
|
require 'statsig_options'
|
7
7
|
require 'statsig_user'
|
8
8
|
require 'spec_store'
|
9
|
+
require 'dynamic_config'
|
10
|
+
require 'layer'
|
9
11
|
|
10
12
|
class StatsigDriver
|
11
13
|
def initialize(secret_key, options = nil, error_callback = nil)
|
@@ -67,11 +69,11 @@ class StatsigDriver
|
|
67
69
|
end
|
68
70
|
res = get_config_fallback(user, res.config_delegate)
|
69
71
|
# exposure logged by the server
|
70
|
-
else
|
71
|
-
@logger.log_layer_exposure(user, res.name, res.rule_id, res.secondary_exposures, res.config_delegate)
|
72
72
|
end
|
73
73
|
|
74
|
-
Layer.new(res.name, res.json_value, res.rule_id
|
74
|
+
Layer.new(res.name, res.json_value, res.rule_id, lambda { |layer, parameter_name|
|
75
|
+
@logger.log_layer_exposure(user, layer, parameter_name, res)
|
76
|
+
})
|
75
77
|
end
|
76
78
|
|
77
79
|
def log_event(user, event_name, value = nil, metadata = nil)
|
@@ -101,7 +103,7 @@ class StatsigDriver
|
|
101
103
|
def verify_inputs(user, config_name, variable_name)
|
102
104
|
validate_user(user)
|
103
105
|
if !config_name.is_a?(String) || config_name.empty?
|
104
|
-
raise "Invalid
|
106
|
+
raise "Invalid #{variable_name} provided"
|
105
107
|
end
|
106
108
|
|
107
109
|
check_shutdown
|
data/lib/statsig_logger.rb
CHANGED
@@ -44,16 +44,26 @@ module Statsig
|
|
44
44
|
log_event(event)
|
45
45
|
end
|
46
46
|
|
47
|
-
def log_layer_exposure(user,
|
47
|
+
def log_layer_exposure(user, layer, parameter_name, config_evaluation)
|
48
|
+
exposures = config_evaluation.undelegated_sec_exps
|
49
|
+
allocated_experiment = ''
|
50
|
+
is_explicit = config_evaluation.explicit_parameters.include? parameter_name
|
51
|
+
if is_explicit
|
52
|
+
allocated_experiment = config_evaluation.config_delegate
|
53
|
+
exposures = config_evaluation.secondary_exposures
|
54
|
+
end
|
55
|
+
|
48
56
|
event = StatsigEvent.new($layer_exposure_event)
|
49
57
|
event.user = user
|
50
58
|
event.metadata = {
|
51
|
-
'config' =>
|
52
|
-
'ruleID' => rule_id,
|
53
|
-
'allocatedExperiment' => allocated_experiment
|
59
|
+
'config' => layer.name,
|
60
|
+
'ruleID' => layer.rule_id,
|
61
|
+
'allocatedExperiment' => allocated_experiment,
|
62
|
+
'parameterName' => parameter_name,
|
63
|
+
'isExplicitParameter' => String(is_explicit)
|
54
64
|
}
|
55
65
|
event.statsig_metadata = Statsig.get_statsig_metadata
|
56
|
-
event.secondary_exposures =
|
66
|
+
event.secondary_exposures = exposures.is_a?(Array) ? exposures : []
|
57
67
|
log_event(event)
|
58
68
|
end
|
59
69
|
|
data/lib/statsig_options.rb
CHANGED
@@ -2,7 +2,7 @@ class StatsigOptions
|
|
2
2
|
attr_reader :environment
|
3
3
|
attr_reader :api_url_base
|
4
4
|
|
5
|
-
def initialize(environment = nil, api_url_base = 'https://
|
5
|
+
def initialize(environment = nil, api_url_base = 'https://statsigapi.net/v1')
|
6
6
|
@environment = environment.is_a?(Hash) ? environment : nil
|
7
7
|
@api_url_base = api_url_base
|
8
8
|
end
|
data/lib/statsig_user.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: statsig
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.9.
|
4
|
+
version: 1.9.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Statsig, Inc
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-04-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -118,6 +118,7 @@ files:
|
|
118
118
|
- lib/dynamic_config.rb
|
119
119
|
- lib/evaluation_helpers.rb
|
120
120
|
- lib/evaluator.rb
|
121
|
+
- lib/id_list.rb
|
121
122
|
- lib/layer.rb
|
122
123
|
- lib/network.rb
|
123
124
|
- lib/spec_store.rb
|