statsig 1.25.2 → 1.30.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/client_initialize_helpers.rb +41 -5
- data/lib/config_result.rb +49 -3
- data/lib/diagnostics.rb +51 -24
- data/lib/dynamic_config.rb +15 -2
- data/lib/error_boundary.rb +32 -44
- data/lib/evaluation_details.rb +12 -7
- data/lib/evaluator.rb +79 -21
- data/lib/feature_gate.rb +70 -0
- data/lib/hash_utils.rb +32 -0
- data/lib/id_list.rb +1 -1
- data/lib/interfaces/user_persistent_storage.rb +12 -0
- data/lib/layer.rb +17 -3
- data/lib/network.rb +94 -45
- data/lib/spec_store.rb +129 -55
- data/lib/statsig.rb +121 -21
- data/lib/statsig_driver.rb +193 -93
- data/lib/statsig_errors.rb +7 -0
- data/lib/statsig_logger.rb +26 -17
- data/lib/statsig_options.rb +33 -9
- data/lib/statsig_user.rb +53 -4
- data/lib/ua_parser.rb +1 -0
- data/lib/uri_helper.rb +1 -1
- data/lib/user_persistent_storage_utils.rb +106 -0
- metadata +58 -6
data/lib/evaluator.rb
CHANGED
@@ -1,4 +1,6 @@
|
|
1
1
|
# typed: false
|
2
|
+
|
3
|
+
require 'sorbet-runtime'
|
2
4
|
require 'config_result'
|
3
5
|
require 'country_lookup'
|
4
6
|
require 'digest'
|
@@ -9,21 +11,40 @@ require 'time'
|
|
9
11
|
require 'ua_parser'
|
10
12
|
require 'evaluation_details'
|
11
13
|
require 'user_agent_parser/operating_system'
|
14
|
+
require 'user_persistent_storage_utils'
|
12
15
|
|
13
16
|
$fetch_from_server = 'fetch_from_server'
|
14
17
|
$type_dynamic_config = 'dynamic_config'
|
15
18
|
|
16
19
|
module Statsig
|
17
20
|
class Evaluator
|
21
|
+
extend T::Sig
|
22
|
+
|
23
|
+
sig { returns(SpecStore) }
|
18
24
|
attr_accessor :spec_store
|
19
25
|
|
20
|
-
|
21
|
-
|
26
|
+
sig { returns(StatsigOptions) }
|
27
|
+
attr_accessor :options
|
28
|
+
|
29
|
+
sig { returns(UserPersistentStorageUtils) }
|
30
|
+
attr_accessor :persistent_storage_utils
|
31
|
+
|
32
|
+
sig do
|
33
|
+
params(
|
34
|
+
store: SpecStore,
|
35
|
+
options: StatsigOptions,
|
36
|
+
persistent_storage_utils: UserPersistentStorageUtils,
|
37
|
+
).void
|
38
|
+
end
|
39
|
+
def initialize(store, options, persistent_storage_utils)
|
22
40
|
UAParser.initialize_async
|
23
41
|
CountryLookup.initialize_async
|
24
42
|
|
43
|
+
@spec_store = store
|
25
44
|
@gate_overrides = {}
|
26
45
|
@config_overrides = {}
|
46
|
+
@options = options
|
47
|
+
@persistent_storage_utils = persistent_storage_utils
|
27
48
|
end
|
28
49
|
|
29
50
|
def maybe_restart_background_threads
|
@@ -52,7 +73,8 @@ module Statsig
|
|
52
73
|
eval_spec(user, @spec_store.get_gate(gate_name))
|
53
74
|
end
|
54
75
|
|
55
|
-
|
76
|
+
sig { params(user: StatsigUser, config_name: String, user_persisted_values: T.nilable(UserPersistedValues)).returns(ConfigResult) }
|
77
|
+
def get_config(user, config_name, user_persisted_values: nil)
|
56
78
|
if @config_overrides.key?(config_name)
|
57
79
|
id_type = @spec_store.has_config?(config_name) ? @spec_store.get_config(config_name)['idType'] : ''
|
58
80
|
return Statsig::ConfigResult.new(
|
@@ -83,7 +105,26 @@ module Statsig
|
|
83
105
|
)
|
84
106
|
end
|
85
107
|
|
86
|
-
|
108
|
+
config = @spec_store.get_config(config_name)
|
109
|
+
|
110
|
+
# If persisted values is provided and the experiment is active, return sticky values if exists.
|
111
|
+
if !user_persisted_values.nil? && config['isActive'] == true
|
112
|
+
sticky_result = Statsig::ConfigResult.from_user_persisted_values(config_name, user_persisted_values)
|
113
|
+
return sticky_result unless sticky_result.nil?
|
114
|
+
|
115
|
+
# If it doesn't exist, then save to persisted storage if the user was assigned to an experiment group.
|
116
|
+
evaluation = eval_spec(user, config)
|
117
|
+
if evaluation.is_experiment_group
|
118
|
+
@persistent_storage_utils.add_evaluation_to_user_persisted_values(user_persisted_values, config_name, evaluation)
|
119
|
+
@persistent_storage_utils.save_to_storage(user, config['idType'], user_persisted_values)
|
120
|
+
end
|
121
|
+
# Otherwise, remove from persisted storage
|
122
|
+
else
|
123
|
+
@persistent_storage_utils.remove_experiment_from_storage(user, config['idType'], config_name)
|
124
|
+
evaluation = eval_spec(user, config)
|
125
|
+
end
|
126
|
+
|
127
|
+
return evaluation
|
87
128
|
end
|
88
129
|
|
89
130
|
def get_layer(user, layer_name)
|
@@ -98,12 +139,32 @@ module Statsig
|
|
98
139
|
eval_spec(user, @spec_store.get_layer(layer_name))
|
99
140
|
end
|
100
141
|
|
101
|
-
def
|
142
|
+
def list_gates
|
143
|
+
@spec_store.gates.map { |name, _| name }
|
144
|
+
end
|
145
|
+
|
146
|
+
def list_configs
|
147
|
+
@spec_store.configs.map { |name, config| name if config['entity'] == 'dynamic_config' }.compact
|
148
|
+
end
|
149
|
+
|
150
|
+
def list_experiments
|
151
|
+
@spec_store.configs.map { |name, config| name if config['entity'] == 'experiment' }.compact
|
152
|
+
end
|
153
|
+
|
154
|
+
def list_autotunes
|
155
|
+
@spec_store.configs.map { |name, config| name if config['entity'] == 'autotune' }.compact
|
156
|
+
end
|
157
|
+
|
158
|
+
def list_layers
|
159
|
+
@spec_store.layers.map { |name, _| name }
|
160
|
+
end
|
161
|
+
|
162
|
+
def get_client_initialize_response(user, hash, client_sdk_key)
|
102
163
|
if @spec_store.is_ready_for_checks == false
|
103
164
|
return nil
|
104
165
|
end
|
105
166
|
|
106
|
-
formatter = ClientInitializeHelpers::ResponseFormatter.new(self, user)
|
167
|
+
formatter = ClientInitializeHelpers::ResponseFormatter.new(self, user, hash, client_sdk_key)
|
107
168
|
|
108
169
|
evaluated_keys = {}
|
109
170
|
if user.user_id.nil? == false
|
@@ -123,6 +184,8 @@ module Statsig
|
|
123
184
|
"generator" => "statsig-ruby-sdk",
|
124
185
|
"evaluated_keys" => evaluated_keys,
|
125
186
|
"time" => 0,
|
187
|
+
"hash_used" => hash,
|
188
|
+
"user_hash" => user.to_hash_without_stable_id()
|
126
189
|
}
|
127
190
|
end
|
128
191
|
|
@@ -148,6 +211,7 @@ module Statsig
|
|
148
211
|
@config_overrides[config] = value
|
149
212
|
end
|
150
213
|
|
214
|
+
sig { params(user: StatsigUser, config: Hash).returns(ConfigResult) }
|
151
215
|
def eval_spec(user, config)
|
152
216
|
default_rule_id = 'default'
|
153
217
|
exposures = []
|
@@ -178,7 +242,8 @@ module Statsig
|
|
178
242
|
),
|
179
243
|
is_experiment_group: result.is_experiment_group,
|
180
244
|
group_name: result.group_name,
|
181
|
-
id_type: config['idType']
|
245
|
+
id_type: config['idType'],
|
246
|
+
target_app_ids: config['targetAppIDs']
|
182
247
|
)
|
183
248
|
end
|
184
249
|
|
@@ -200,7 +265,8 @@ module Statsig
|
|
200
265
|
@spec_store.init_reason
|
201
266
|
),
|
202
267
|
group_name: nil,
|
203
|
-
id_type: config['idType']
|
268
|
+
id_type: config['idType'],
|
269
|
+
target_app_ids: config['targetAppIDs']
|
204
270
|
)
|
205
271
|
end
|
206
272
|
|
@@ -264,7 +330,7 @@ module Statsig
|
|
264
330
|
operator = condition['operator']
|
265
331
|
additional_values = condition['additionalValues']
|
266
332
|
additional_values = Hash.new unless additional_values.is_a? Hash
|
267
|
-
|
333
|
+
id_type = condition['idType']
|
268
334
|
|
269
335
|
return $fetch_from_server unless type.is_a? String
|
270
336
|
type = type.downcase
|
@@ -302,14 +368,14 @@ module Statsig
|
|
302
368
|
when 'user_bucket'
|
303
369
|
begin
|
304
370
|
salt = additional_values['salt']
|
305
|
-
unit_id = get_unit_id(
|
371
|
+
unit_id = user.get_unit_id(id_type) || ''
|
306
372
|
# there are only 1000 user buckets as opposed to 10k for gate pass %
|
307
373
|
value = compute_user_hash("#{salt}.#{unit_id}") % 1000
|
308
374
|
rescue
|
309
375
|
return false
|
310
376
|
end
|
311
377
|
when 'unit_id'
|
312
|
-
value = get_unit_id(
|
378
|
+
value = user.get_unit_id(id_type)
|
313
379
|
else
|
314
380
|
return $fetch_from_server
|
315
381
|
end
|
@@ -359,7 +425,7 @@ module Statsig
|
|
359
425
|
when 'none_case_sensitive'
|
360
426
|
return !EvaluationHelpers::match_string_in_array(target, value, false, ->(a, b) { a == b })
|
361
427
|
|
362
|
-
#string
|
428
|
+
# string
|
363
429
|
when 'str_starts_with_any'
|
364
430
|
return EvaluationHelpers::match_string_in_array(target, value, true, ->(a, b) { a.start_with?(b) })
|
365
431
|
when 'str_ends_with_any'
|
@@ -468,7 +534,7 @@ module Statsig
|
|
468
534
|
def eval_pass_percent(user, rule, config_salt)
|
469
535
|
return false unless config_salt.is_a?(String) && !rule['passPercentage'].nil?
|
470
536
|
begin
|
471
|
-
unit_id = get_unit_id(
|
537
|
+
unit_id = user.get_unit_id(rule['idType']) || ''
|
472
538
|
rule_salt = rule['salt'] || rule['id'] || ''
|
473
539
|
hash = compute_user_hash("#{config_salt}.#{rule_salt}.#{unit_id}")
|
474
540
|
return (hash % 10000) < (rule['passPercentage'].to_f * 100)
|
@@ -477,14 +543,6 @@ module Statsig
|
|
477
543
|
end
|
478
544
|
end
|
479
545
|
|
480
|
-
def get_unit_id(user, id_type)
|
481
|
-
if id_type.is_a?(String) && id_type.downcase != 'userid'
|
482
|
-
return nil unless user&.custom_ids.is_a? Hash
|
483
|
-
return user.custom_ids[id_type] || user.custom_ids[id_type.downcase]
|
484
|
-
end
|
485
|
-
user.user_id
|
486
|
-
end
|
487
|
-
|
488
546
|
def compute_user_hash(user_hash)
|
489
547
|
Digest::SHA256.digest(user_hash).unpack('Q>')[0]
|
490
548
|
end
|
data/lib/feature_gate.rb
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
# typed: false
|
2
|
+
|
3
|
+
require 'sorbet-runtime'
|
4
|
+
|
5
|
+
class FeatureGate
|
6
|
+
extend T::Sig
|
7
|
+
|
8
|
+
sig { returns(String) }
|
9
|
+
attr_accessor :name
|
10
|
+
|
11
|
+
sig { returns(T::Boolean) }
|
12
|
+
attr_accessor :value
|
13
|
+
|
14
|
+
sig { returns(String) }
|
15
|
+
attr_accessor :rule_id
|
16
|
+
|
17
|
+
sig { returns(T.nilable(String)) }
|
18
|
+
attr_accessor :group_name
|
19
|
+
|
20
|
+
sig { returns(String) }
|
21
|
+
attr_accessor :id_type
|
22
|
+
|
23
|
+
sig { returns(T.nilable(Statsig::EvaluationDetails)) }
|
24
|
+
attr_accessor :evaluation_details
|
25
|
+
|
26
|
+
sig { returns(T.nilable(T::Array[String])) }
|
27
|
+
attr_accessor :target_app_ids
|
28
|
+
|
29
|
+
sig do
|
30
|
+
params(
|
31
|
+
name: String,
|
32
|
+
value: T::Boolean,
|
33
|
+
rule_id: String,
|
34
|
+
group_name: T.nilable(String),
|
35
|
+
id_type: String,
|
36
|
+
evaluation_details: T.nilable(Statsig::EvaluationDetails),
|
37
|
+
target_app_ids: T.nilable(T::Array[String])
|
38
|
+
).void
|
39
|
+
end
|
40
|
+
def initialize(
|
41
|
+
name,
|
42
|
+
value: false,
|
43
|
+
rule_id: '',
|
44
|
+
group_name: nil,
|
45
|
+
id_type: '',
|
46
|
+
evaluation_details: nil,
|
47
|
+
target_app_ids: nil
|
48
|
+
)
|
49
|
+
@name = name
|
50
|
+
@value = value
|
51
|
+
@rule_id = rule_id
|
52
|
+
@group_name = group_name
|
53
|
+
@id_type = id_type
|
54
|
+
@evaluation_details = evaluation_details
|
55
|
+
@target_app_ids = target_app_ids
|
56
|
+
end
|
57
|
+
|
58
|
+
sig { params(res: Statsig::ConfigResult).returns(FeatureGate) }
|
59
|
+
def self.from_config_result(res)
|
60
|
+
new(
|
61
|
+
res.name,
|
62
|
+
value: res.gate_value,
|
63
|
+
rule_id: res.rule_id,
|
64
|
+
group_name: res.group_name,
|
65
|
+
id_type: res.id_type,
|
66
|
+
evaluation_details: res.evaluation_details,
|
67
|
+
target_app_ids: res.target_app_ids
|
68
|
+
)
|
69
|
+
end
|
70
|
+
end
|
data/lib/hash_utils.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'json'
|
2
|
+
module Statsig
|
3
|
+
class HashUtils
|
4
|
+
def self.djb2(input_str)
|
5
|
+
hash = 0
|
6
|
+
input_str.each_char.each do |c|
|
7
|
+
hash = (hash << 5) - hash + c.ord
|
8
|
+
hash &= hash
|
9
|
+
end
|
10
|
+
hash &= 0xFFFFFFFF # Convert to unsigned 32-bit integer
|
11
|
+
return hash.to_s
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.djb2ForHash(input_hash)
|
15
|
+
return djb2(input_hash.to_json)
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.sha256(input_str)
|
19
|
+
return Digest::SHA256.base64digest(input_str)
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.sortHash(input_hash)
|
23
|
+
dictionary = input_hash.clone.sort_by { |key| key }.to_h;
|
24
|
+
input_hash.each do |key, value|
|
25
|
+
if value.is_a?(Hash)
|
26
|
+
dictionary[key] = self.sortHash(value)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
return dictionary
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
data/lib/id_list.rb
CHANGED
data/lib/layer.rb
CHANGED
@@ -17,11 +17,25 @@ class Layer
|
|
17
17
|
sig { returns(String) }
|
18
18
|
attr_accessor :rule_id
|
19
19
|
|
20
|
-
sig {
|
21
|
-
|
20
|
+
sig { returns(String) }
|
21
|
+
attr_accessor :group_name
|
22
|
+
|
23
|
+
sig do
|
24
|
+
params(
|
25
|
+
name: String,
|
26
|
+
value: T::Hash[String, T.untyped],
|
27
|
+
rule_id: String,
|
28
|
+
group_name: T.nilable(String),
|
29
|
+
allocated_experiment: T.nilable(String),
|
30
|
+
exposure_log_func: T.any(Method, Proc, NilClass)
|
31
|
+
).void
|
32
|
+
end
|
33
|
+
def initialize(name, value = {}, rule_id = '', group_name = nil, allocated_experiment = nil, exposure_log_func = nil)
|
22
34
|
@name = name
|
23
35
|
@value = value
|
24
36
|
@rule_id = rule_id
|
37
|
+
@group_name = group_name
|
38
|
+
@allocated_experiment = allocated_experiment
|
25
39
|
@exposure_log_func = exposure_log_func
|
26
40
|
end
|
27
41
|
|
@@ -58,4 +72,4 @@ class Layer
|
|
58
72
|
|
59
73
|
@value[index]
|
60
74
|
end
|
61
|
-
end
|
75
|
+
end
|
data/lib/network.rb
CHANGED
@@ -5,8 +5,9 @@ require 'json'
|
|
5
5
|
require 'securerandom'
|
6
6
|
require 'sorbet-runtime'
|
7
7
|
require 'uri_helper'
|
8
|
+
require 'connection_pool'
|
8
9
|
|
9
|
-
|
10
|
+
RETRY_CODES = [408, 500, 502, 503, 504, 522, 524, 599].freeze
|
10
11
|
|
11
12
|
module Statsig
|
12
13
|
class NetworkError < StandardError
|
@@ -22,7 +23,6 @@ module Statsig
|
|
22
23
|
extend T::Sig
|
23
24
|
|
24
25
|
sig { params(server_secret: String, options: StatsigOptions, backoff_mult: Integer).void }
|
25
|
-
|
26
26
|
def initialize(server_secret, options, backoff_mult = 10)
|
27
27
|
super()
|
28
28
|
URIHelper.initialize(options)
|
@@ -33,29 +33,67 @@ module Statsig
|
|
33
33
|
@post_logs_retry_backoff = options.post_logs_retry_backoff
|
34
34
|
@post_logs_retry_limit = options.post_logs_retry_limit
|
35
35
|
@session_id = SecureRandom.uuid
|
36
|
+
@connection_pool = ConnectionPool.new(size: 3) do
|
37
|
+
meta = Statsig.get_statsig_metadata
|
38
|
+
client = HTTP.use(:auto_inflate).headers(
|
39
|
+
{
|
40
|
+
'STATSIG-API-KEY' => @server_secret,
|
41
|
+
'STATSIG-SERVER-SESSION-ID' => @session_id,
|
42
|
+
'Content-Type' => 'application/json; charset=UTF-8',
|
43
|
+
'STATSIG-SDK-TYPE' => meta['sdkType'],
|
44
|
+
'STATSIG-SDK-VERSION' => meta['sdkVersion'],
|
45
|
+
'STATSIG-SDK-LANGUAGE-VERSION' => meta['languageVersion'],
|
46
|
+
'Accept-Encoding' => 'gzip'
|
47
|
+
}
|
48
|
+
).accept(:json)
|
49
|
+
if @timeout
|
50
|
+
client = client.timeout(@timeout)
|
51
|
+
end
|
52
|
+
|
53
|
+
client
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
sig do
|
58
|
+
params(since_time: Integer)
|
59
|
+
.returns([T.any(HTTP::Response, NilClass), T.any(StandardError, NilClass)])
|
60
|
+
end
|
61
|
+
def download_config_specs(since_time)
|
62
|
+
get("download_config_specs/#{@server_secret}.json?sinceTime=#{since_time}")
|
63
|
+
end
|
64
|
+
|
65
|
+
class HttpMethod < T::Enum
|
66
|
+
enums do
|
67
|
+
GET = new
|
68
|
+
POST = new
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
sig do
|
73
|
+
params(endpoint: String, retries: Integer, backoff: Integer)
|
74
|
+
.returns([T.any(HTTP::Response, NilClass), T.any(StandardError, NilClass)])
|
75
|
+
end
|
76
|
+
def get(endpoint, retries = 0, backoff = 1)
|
77
|
+
request(HttpMethod::GET, endpoint, nil, retries, backoff)
|
36
78
|
end
|
37
79
|
|
38
|
-
sig
|
39
|
-
|
80
|
+
sig do
|
81
|
+
params(endpoint: String, body: String, retries: Integer, backoff: Integer)
|
82
|
+
.returns([T.any(HTTP::Response, NilClass), T.any(StandardError, NilClass)])
|
83
|
+
end
|
84
|
+
def post(endpoint, body, retries = 0, backoff = 1)
|
85
|
+
request(HttpMethod::POST, endpoint, body, retries, backoff)
|
86
|
+
end
|
40
87
|
|
41
|
-
|
88
|
+
sig do
|
89
|
+
params(method: HttpMethod, endpoint: String, body: T.nilable(String), retries: Integer, backoff: Integer)
|
90
|
+
.returns([T.any(HTTP::Response, NilClass), T.any(StandardError, NilClass)])
|
91
|
+
end
|
92
|
+
def request(method, endpoint, body, retries = 0, backoff = 1)
|
42
93
|
if @local_mode
|
43
94
|
return nil, nil
|
44
95
|
end
|
45
96
|
|
46
|
-
meta = Statsig.get_statsig_metadata
|
47
|
-
http = HTTP.headers(
|
48
|
-
{
|
49
|
-
"STATSIG-API-KEY" => @server_secret,
|
50
|
-
"STATSIG-CLIENT-TIME" => (Time.now.to_f * 1000).to_i.to_s,
|
51
|
-
"STATSIG-SERVER-SESSION-ID" => @session_id,
|
52
|
-
"Content-Type" => "application/json; charset=UTF-8",
|
53
|
-
"STATSIG-SDK-TYPE" => meta['sdkType'],
|
54
|
-
"STATSIG-SDK-VERSION" => meta['sdkVersion'],
|
55
|
-
}).accept(:json)
|
56
|
-
if @timeout
|
57
|
-
http = http.timeout(@timeout)
|
58
|
-
end
|
59
97
|
backoff_adjusted = backoff > 10 ? backoff += Random.rand(10) : backoff # to deter overlap
|
60
98
|
if @post_logs_retry_backoff
|
61
99
|
if @post_logs_retry_backoff.is_a? Integer
|
@@ -66,48 +104,59 @@ module Statsig
|
|
66
104
|
end
|
67
105
|
url = URIHelper.build_url(endpoint)
|
68
106
|
begin
|
69
|
-
res =
|
107
|
+
res = @connection_pool.with do |conn|
|
108
|
+
request = conn.headers('STATSIG-CLIENT-TIME' => (Time.now.to_f * 1000).to_i.to_s)
|
109
|
+
case method
|
110
|
+
when HttpMethod::GET
|
111
|
+
request.get(url)
|
112
|
+
when HttpMethod::POST
|
113
|
+
request.post(url, body: body)
|
114
|
+
end
|
115
|
+
end
|
70
116
|
rescue StandardError => e
|
71
117
|
## network error retry
|
72
|
-
return nil, e unless retries
|
118
|
+
return nil, e unless retries.positive?
|
119
|
+
|
73
120
|
sleep backoff_adjusted
|
74
|
-
return
|
121
|
+
return request(method, endpoint, body, retries - 1, backoff * @backoff_multiplier)
|
75
122
|
end
|
76
123
|
return res, nil if res.status.success?
|
77
|
-
|
124
|
+
|
125
|
+
unless retries.positive? && RETRY_CODES.include?(res.code)
|
126
|
+
return res, NetworkError.new("Got an exception when making request to #{url}: #{res.to_s}",
|
127
|
+
res.status.to_i)
|
128
|
+
end
|
129
|
+
|
78
130
|
## status code retry
|
79
131
|
sleep backoff_adjusted
|
80
|
-
|
132
|
+
request(method, endpoint, body, retries - 1, backoff * @backoff_multiplier)
|
81
133
|
end
|
82
134
|
|
83
135
|
def check_gate(user, gate_name)
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
end
|
136
|
+
request_body = JSON.generate({ 'user' => user&.serialize(false), 'gateName' => gate_name })
|
137
|
+
response, = post('check_gate', request_body)
|
138
|
+
return JSON.parse(response.body) unless response.nil?
|
139
|
+
|
140
|
+
false
|
141
|
+
rescue StandardError
|
142
|
+
false
|
92
143
|
end
|
93
144
|
|
94
145
|
def get_config(user, dynamic_config_name)
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
end
|
146
|
+
request_body = JSON.generate({ 'user' => user&.serialize(false), 'configName' => dynamic_config_name })
|
147
|
+
response, = post('get_config', request_body)
|
148
|
+
return JSON.parse(response.body) unless response.nil?
|
149
|
+
|
150
|
+
nil
|
151
|
+
rescue StandardError
|
152
|
+
nil
|
103
153
|
end
|
104
154
|
|
105
155
|
def post_logs(events)
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
end
|
156
|
+
json_body = JSON.generate({ 'events' => events, 'statsigMetadata' => Statsig.get_statsig_metadata })
|
157
|
+
post('log_event', json_body, @post_logs_retry_limit)
|
158
|
+
rescue StandardError
|
159
|
+
|
111
160
|
end
|
112
161
|
end
|
113
|
-
end
|
162
|
+
end
|