statsig 1.32.0 → 1.33.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dd71ed81ad0f3658f5b6b480adba4a36f560357c67d6d9869b605e2d0e1e76c0
4
- data.tar.gz: 59b7da08c4991da0e7b33dc08865612347442970932e71b8069bcd4518d50680
3
+ metadata.gz: 56f4bfc568fa70750a5dc1376ffffacd1163344c63750492e0e0ff841fd73d18
4
+ data.tar.gz: 6e41d012096437a58a9ceea49c0aa5d71532d06d273b37593afcd7cb9bc07297
5
5
  SHA512:
6
- metadata.gz: 3775510747a856d9ae300ccad20a74de3cbe000bc3bca59c535c64cbab1bcb46a83069aa917299ee6dd7032f44206eecd39f3602c92f18c39a8ebbb57b45d9e9
7
- data.tar.gz: e0a03db37029a260614a6eb8bf9edf1e2fe60f84d67fb9e027bf5fd72c3c4f969a7ab3145f55d0bd19f796b7d1733416547688e1076c69e066abd819c99b899c
6
+ metadata.gz: 7060859db57bed3e98e372c1b3808f53802bb1c7e4e76f70b366e138c022d1aef522605457d8e69f8eb029394c8bb8c32e1aa1eb0933d4fcd8746f60452afac3
7
+ data.tar.gz: a0857b56731a9fab6d53748700a932fc3d6a3c7b065dd8ce54d03bf89274002f215e6ae39fa0b72d49c60cb4c7cc11307f7e4d3b1f965f7f4f5a721545712978
@@ -4,10 +4,18 @@ require 'constants'
4
4
 
5
5
  module Statsig
6
6
  class ResponseFormatter
7
- def self.get_responses(entities, evaluator, user, client_sdk_key, hash_algo, include_exposures: true)
7
+ def self.get_responses(
8
+ entities,
9
+ evaluator,
10
+ user,
11
+ client_sdk_key,
12
+ hash_algo,
13
+ include_exposures: true,
14
+ include_local_overrides: false
15
+ )
8
16
  result = {}
9
17
  entities.each do |name, spec|
10
- hashed_name, value = to_response(name, spec, evaluator, user, client_sdk_key, hash_algo, include_exposures)
18
+ hashed_name, value = to_response(name, spec, evaluator, user, client_sdk_key, hash_algo, include_exposures, include_local_overrides)
11
19
  if !hashed_name.nil? && !value.nil?
12
20
  result[hashed_name] = value
13
21
  end
@@ -16,7 +24,7 @@ module Statsig
16
24
  result
17
25
  end
18
26
 
19
- def self.to_response(config_name, config_spec, evaluator, user, client_sdk_key, hash_algo, include_exposures)
27
+ def self.to_response(config_name, config_spec, evaluator, user, client_sdk_key, hash_algo, include_exposures, include_local_overrides)
20
28
  target_app_id = evaluator.spec_store.get_app_id_for_sdk_key(client_sdk_key)
21
29
  config_target_apps = config_spec.target_app_ids
22
30
 
@@ -30,12 +38,25 @@ module Statsig
30
38
  return nil
31
39
  end
32
40
 
33
- eval_result = ConfigResult.new(
34
- name: config_name,
35
- disable_evaluation_details: true,
36
- disable_exposures: !include_exposures
37
- )
38
- evaluator.eval_spec(user, config_spec, eval_result)
41
+ if include_local_overrides
42
+ case category
43
+ when :feature_gate
44
+ local_override = evaluator.lookup_gate_override(config_name)
45
+ when :dynamic_config
46
+ local_override = evaluator.lookup_config_override(config_name)
47
+ end
48
+ end
49
+
50
+ if local_override.nil?
51
+ eval_result = ConfigResult.new(
52
+ name: config_name,
53
+ disable_evaluation_details: true,
54
+ disable_exposures: !include_exposures
55
+ )
56
+ evaluator.eval_spec(user, config_spec, eval_result)
57
+ else
58
+ eval_result = local_override
59
+ end
39
60
 
40
61
  result = {}
41
62
 
data/lib/evaluator.rb CHANGED
@@ -16,6 +16,10 @@ module Statsig
16
16
 
17
17
  attr_accessor :spec_store
18
18
 
19
+ attr_accessor :gate_overrides
20
+
21
+ attr_accessor :config_overrides
22
+
19
23
  attr_accessor :options
20
24
 
21
25
  attr_accessor :persistent_storage_utils
@@ -35,17 +39,49 @@ module Statsig
35
39
  @spec_store.maybe_restart_background_threads
36
40
  end
37
41
 
38
- def check_gate(user, gate_name, end_result)
42
+ def lookup_gate_override(gate_name)
39
43
  if @gate_overrides.key?(gate_name)
40
- end_result.gate_value = @gate_overrides[gate_name]
41
- end_result.rule_id = Const::OVERRIDE
42
- unless end_result.disable_evaluation_details
43
- end_result.evaluation_details = EvaluationDetails.local_override(
44
+ return ConfigResult.new(
45
+ name: gate_name,
46
+ gate_value: @gate_overrides[gate_name],
47
+ rule_id: Const::OVERRIDE,
48
+ id_type: @spec_store.has_gate?(gate_name) ? @spec_store.get_gate(gate_name).id_type : Const::EMPTY_STR,
49
+ evaluation_details: EvaluationDetails.local_override(
50
+ @spec_store.last_config_sync_time,
51
+ @spec_store.initial_config_sync_time
52
+ )
53
+ )
54
+ end
55
+ return nil
56
+ end
57
+
58
+ def lookup_config_override(config_name)
59
+ if @config_overrides.key?(config_name)
60
+ return ConfigResult.new(
61
+ name: config_name,
62
+ json_value: @config_overrides[config_name],
63
+ rule_id: Const::OVERRIDE,
64
+ id_type: @spec_store.has_config?(config_name) ? @spec_store.get_config(config_name).id_type : Const::EMPTY_STR,
65
+ evaluation_details: EvaluationDetails.local_override(
44
66
  @spec_store.last_config_sync_time,
45
67
  @spec_store.initial_config_sync_time
46
68
  )
69
+ )
70
+ end
71
+ return nil
72
+ end
73
+
74
+ def check_gate(user, gate_name, end_result, ignore_local_overrides: false)
75
+ unless ignore_local_overrides
76
+ local_override = lookup_gate_override(gate_name)
77
+ unless local_override.nil?
78
+ end_result.gate_value = local_override.gate_value
79
+ end_result.rule_id = local_override.rule_id
80
+ unless end_result.disable_evaluation_details
81
+ end_result.evaluation_details = local_override.evaluation_details
82
+ end
83
+ return
47
84
  end
48
- return
49
85
  end
50
86
 
51
87
  if @spec_store.init_reason == EvaluationReason::UNINITIALIZED
@@ -63,19 +99,18 @@ module Statsig
63
99
  eval_spec(user, @spec_store.get_gate(gate_name), end_result)
64
100
  end
65
101
 
66
- def get_config(user, config_name, end_result, user_persisted_values: nil)
67
- if @config_overrides.key?(config_name)
68
- id_type = @spec_store.has_config?(config_name) ? @spec_store.get_config(config_name).id_type : Const::EMPTY_STR
69
- end_result.id_type = id_type
70
- end_result.rule_id = Const::OVERRIDE
71
- end_result.json_value = @config_overrides[config_name]
72
- unless end_result.disable_evaluation_details
73
- end_result.evaluation_details = EvaluationDetails.local_override(
74
- @spec_store.last_config_sync_time,
75
- @spec_store.initial_config_sync_time
76
- )
102
+ def get_config(user, config_name, end_result, user_persisted_values: nil, ignore_local_overrides: false)
103
+ unless ignore_local_overrides
104
+ local_override = lookup_config_override(config_name)
105
+ unless local_override.nil?
106
+ end_result.id_type = local_override.id_type
107
+ end_result.rule_id = local_override.rule_id
108
+ end_result.json_value = local_override.json_value
109
+ unless end_result.disable_evaluation_details
110
+ end_result.evaluation_details = local_override.evaluation_details
111
+ end
112
+ return
77
113
  end
78
- return
79
114
  end
80
115
 
81
116
  if @spec_store.init_reason == EvaluationReason::UNINITIALIZED
@@ -162,7 +197,7 @@ module Statsig
162
197
  @spec_store.layers.map { |name, _| name }
163
198
  end
164
199
 
165
- def get_client_initialize_response(user, hash_algo, client_sdk_key)
200
+ def get_client_initialize_response(user, hash_algo, client_sdk_key, include_local_overrides)
166
201
  if @spec_store.is_ready_for_checks == false
167
202
  return nil
168
203
  end
@@ -178,11 +213,11 @@ module Statsig
178
213
 
179
214
  {
180
215
  feature_gates: Statsig::ResponseFormatter
181
- .get_responses(@spec_store.gates, self, user, client_sdk_key, hash_algo),
216
+ .get_responses(@spec_store.gates, self, user, client_sdk_key, hash_algo, include_local_overrides: include_local_overrides),
182
217
  dynamic_configs: Statsig::ResponseFormatter
183
- .get_responses(@spec_store.configs, self, user, client_sdk_key, hash_algo),
218
+ .get_responses(@spec_store.configs, self, user, client_sdk_key, hash_algo, include_local_overrides: include_local_overrides),
184
219
  layer_configs: Statsig::ResponseFormatter
185
- .get_responses(@spec_store.layers, self, user, client_sdk_key, hash_algo),
220
+ .get_responses(@spec_store.layers, self, user, client_sdk_key, hash_algo, include_local_overrides: include_local_overrides),
186
221
  sdkParams: {},
187
222
  has_updates: true,
188
223
  generator: Const::STATSIG_RUBY_SDK,
@@ -222,10 +257,26 @@ module Statsig
222
257
  @gate_overrides[gate] = value
223
258
  end
224
259
 
260
+ def remove_gate_override(gate)
261
+ @gate_overrides.delete(gate)
262
+ end
263
+
264
+ def clear_gate_overrides
265
+ @gate_overrides.clear
266
+ end
267
+
225
268
  def override_config(config, value)
226
269
  @config_overrides[config] = value
227
270
  end
228
271
 
272
+ def remove_config_override(config)
273
+ @config_overrides.delete(config)
274
+ end
275
+
276
+ def clear_config_overrides
277
+ @config_overrides.clear
278
+ end
279
+
229
280
  def eval_spec(user, config, end_result)
230
281
  unless config.enabled
231
282
  finalize_eval_result(config, end_result, did_pass: false, rule: nil)
data/lib/statsig.rb CHANGED
@@ -43,11 +43,12 @@ module Statsig
43
43
  end
44
44
 
45
45
  class CheckGateOptions
46
- attr_accessor :disable_log_exposure, :disable_evaluation_details
46
+ attr_accessor :disable_log_exposure, :disable_evaluation_details, :ignore_local_overrides
47
47
 
48
- def initialize(disable_log_exposure: false, disable_evaluation_details: false)
48
+ def initialize(disable_log_exposure: false, disable_evaluation_details: false, ignore_local_overrides: false)
49
49
  @disable_log_exposure = disable_log_exposure
50
50
  @disable_evaluation_details = disable_evaluation_details
51
+ @ignore_local_overrides = ignore_local_overrides
51
52
  end
52
53
  end
53
54
 
@@ -85,11 +86,12 @@ module Statsig
85
86
  end
86
87
 
87
88
  class GetConfigOptions
88
- attr_accessor :disable_log_exposure, :disable_evaluation_details
89
+ attr_accessor :disable_log_exposure, :disable_evaluation_details, :ignore_local_overrides
89
90
 
90
- def initialize(disable_log_exposure: false, disable_evaluation_details: false)
91
+ def initialize(disable_log_exposure: false, disable_evaluation_details: false, ignore_local_overrides: false)
91
92
  @disable_log_exposure = disable_log_exposure
92
93
  @disable_evaluation_details = disable_evaluation_details
94
+ @ignore_local_overrides = ignore_local_overrides
93
95
  end
94
96
  end
95
97
 
@@ -128,12 +130,13 @@ module Statsig
128
130
  end
129
131
 
130
132
  class GetExperimentOptions
131
- attr_accessor :disable_log_exposure, :user_persisted_values, :disable_evaluation_details
133
+ attr_accessor :disable_log_exposure, :user_persisted_values, :disable_evaluation_details, :ignore_local_overrides
132
134
 
133
- def initialize(disable_log_exposure: false, user_persisted_values: nil, disable_evaluation_details: false)
135
+ def initialize(disable_log_exposure: false, user_persisted_values: nil, disable_evaluation_details: false, ignore_local_overrides: false)
134
136
  @disable_log_exposure = disable_log_exposure
135
137
  @user_persisted_values = user_persisted_values
136
138
  @disable_evaluation_details = disable_evaluation_details
139
+ @ignore_local_overrides = ignore_local_overrides
137
140
  end
138
141
  end
139
142
 
@@ -298,6 +301,16 @@ module Statsig
298
301
  @shared_instance&.override_gate(gate_name, gate_value)
299
302
  end
300
303
 
304
+ def self.remove_gate_override(gate_name)
305
+ ensure_initialized
306
+ @shared_instance&.remove_gate_override(gate_name)
307
+ end
308
+
309
+ def self.clear_gate_overrides
310
+ ensure_initialized
311
+ @shared_instance&.clear_gate_overrides
312
+ end
313
+
301
314
  ##
302
315
  # Sets a value to be returned for the given dynamic config/experiment instead of the actual evaluated value.
303
316
  #
@@ -308,6 +321,16 @@ module Statsig
308
321
  @shared_instance&.override_config(config_name, config_value)
309
322
  end
310
323
 
324
+ def self.remove_config_override(config_name)
325
+ ensure_initialized
326
+ @shared_instance&.remove_config_override(config_name)
327
+ end
328
+
329
+ def self.clear_config_overrides
330
+ ensure_initialized
331
+ @shared_instance&.clear_config_overrides
332
+ end
333
+
311
334
  ##
312
335
  # Gets all evaluated values for the given user.
313
336
  # These values can then be given to a Statsig Client SDK via bootstrapping.
@@ -315,11 +338,17 @@ module Statsig
315
338
  # @param user A StatsigUser object used for the evaluation
316
339
  # @param hash The type of hashing algorithm to use ('sha256', 'djb2', 'none')
317
340
  # @param client_sdk_key A optional client sdk key to be used for the evaluation
341
+ # @param include_local_overrides Option to include local overrides
318
342
  #
319
343
  # @note See Ruby Documentation: https://docs.statsig.com/server/rubySDK)
320
- def self.get_client_initialize_response(user, hash: 'sha256', client_sdk_key: nil)
344
+ def self.get_client_initialize_response(
345
+ user,
346
+ hash: 'sha256',
347
+ client_sdk_key: nil,
348
+ include_local_overrides: false
349
+ )
321
350
  ensure_initialized
322
- @shared_instance&.get_client_initialize_response(user, hash, client_sdk_key)
351
+ @shared_instance&.get_client_initialize_response(user, hash, client_sdk_key, include_local_overrides)
323
352
  end
324
353
 
325
354
  ##
@@ -327,7 +356,7 @@ module Statsig
327
356
  def self.get_statsig_metadata
328
357
  {
329
358
  'sdkType' => 'ruby-server',
330
- 'sdkVersion' => '1.32.0',
359
+ 'sdkVersion' => '1.33.0',
331
360
  'languageVersion' => RUBY_VERSION
332
361
  }
333
362
  end
@@ -43,7 +43,14 @@ class StatsigDriver
43
43
  }, caller: __method__.to_s)
44
44
  end
45
45
 
46
- def get_gate_impl(user, gate_name, disable_log_exposure: false, skip_evaluation: false, disable_evaluation_details: false)
46
+ def get_gate_impl(
47
+ user,
48
+ gate_name,
49
+ disable_log_exposure: false,
50
+ skip_evaluation: false,
51
+ disable_evaluation_details: false,
52
+ ignore_local_overrides: false
53
+ )
47
54
  if skip_evaluation
48
55
  gate = @store.get_gate(gate_name)
49
56
  return FeatureGate.new(gate_name) if gate.nil?
@@ -52,7 +59,7 @@ class StatsigDriver
52
59
  user = verify_inputs(user, gate_name, 'gate_name')
53
60
 
54
61
  res = Statsig::ConfigResult.new(name: gate_name, disable_exposures: disable_log_exposure, disable_evaluation_details: disable_evaluation_details)
55
- @evaluator.check_gate(user, gate_name, res)
62
+ @evaluator.check_gate(user, gate_name, res, ignore_local_overrides: ignore_local_overrides)
56
63
 
57
64
  unless disable_log_exposure
58
65
  @logger.log_gate_exposure(
@@ -81,7 +88,8 @@ class StatsigDriver
81
88
  user,
82
89
  gate_name,
83
90
  disable_log_exposure: options&.disable_log_exposure == true,
84
- disable_evaluation_details: options&.disable_evaluation_details == true
91
+ disable_evaluation_details: options&.disable_evaluation_details == true,
92
+ ignore_local_overrides: options&.ignore_local_overrides == true
85
93
  ).value
86
94
  }, caller: __method__.to_s)
87
95
  }, recover: -> { false }, caller: __method__.to_s)
@@ -104,7 +112,8 @@ class StatsigDriver
104
112
  user,
105
113
  dynamic_config_name,
106
114
  options&.disable_log_exposure == true,
107
- disable_evaluation_details: options&.disable_evaluation_details == true
115
+ disable_evaluation_details: options&.disable_evaluation_details == true,
116
+ ignore_local_overrides: options&.ignore_local_overrides == true
108
117
  )
109
118
  }, caller: __method__.to_s)
110
119
  }, recover: -> { DynamicConfig.new(dynamic_config_name) }, caller: __method__.to_s)
@@ -119,7 +128,8 @@ class StatsigDriver
119
128
  experiment_name,
120
129
  options&.disable_log_exposure == true,
121
130
  user_persisted_values: options&.user_persisted_values,
122
- disable_evaluation_details: options&.disable_evaluation_details == true
131
+ disable_evaluation_details: options&.disable_evaluation_details == true,
132
+ ignore_local_overrides: options&.ignore_local_overrides == true
123
133
  )
124
134
  }, caller: __method__.to_s)
125
135
  }, recover: -> { DynamicConfig.new(experiment_name) }, caller: __method__.to_s)
@@ -249,20 +259,45 @@ class StatsigDriver
249
259
  }, caller: __method__.to_s)
250
260
  end
251
261
 
262
+ def remove_gate_override(gate_name)
263
+ @err_boundary.capture(task: lambda {
264
+ @evaluator.remove_gate_override(gate_name)
265
+ }, caller: __method__.to_s)
266
+ end
267
+
268
+ def clear_gate_overrides
269
+ @err_boundary.capture(task: lambda {
270
+ @evaluator.clear_gate_overrides
271
+ }, caller: __method__.to_s)
272
+ end
273
+
252
274
  def override_config(config_name, config_value)
253
275
  @err_boundary.capture(task: lambda {
254
276
  @evaluator.override_config(config_name, config_value)
255
277
  }, caller: __method__.to_s)
256
278
  end
257
279
 
280
+ def remove_config_override(config_name)
281
+ @err_boundary.capture(task: lambda {
282
+ @evaluator.remove_config_override(config_name)
283
+ }, caller: __method__.to_s)
284
+ end
285
+
286
+ def clear_config_overrides
287
+ @err_boundary.capture(task: lambda {
288
+ @evaluator.clear_config_overrides
289
+ }, caller: __method__.to_s)
290
+ end
291
+
258
292
  # @param [StatsigUser] user
259
293
  # @param [String | nil] client_sdk_key
294
+ # @param [Boolean] include_local_overrides
260
295
  # @return [Hash]
261
- def get_client_initialize_response(user, hash, client_sdk_key)
296
+ def get_client_initialize_response(user, hash, client_sdk_key, include_local_overrides)
262
297
  @err_boundary.capture(task: lambda {
263
298
  validate_user(user)
264
299
  normalize_user(user)
265
- @evaluator.get_client_initialize_response(user, hash, client_sdk_key)
300
+ @evaluator.get_client_initialize_response(user, hash, client_sdk_key, include_local_overrides)
266
301
  }, recover: -> { nil }, caller: __method__.to_s)
267
302
  end
268
303
 
@@ -308,13 +343,13 @@ class StatsigDriver
308
343
  normalize_user(user)
309
344
  end
310
345
 
311
- def get_config_impl(user, config_name, disable_log_exposure, user_persisted_values: nil, disable_evaluation_details: false)
346
+ def get_config_impl(user, config_name, disable_log_exposure, user_persisted_values: nil, disable_evaluation_details: false, ignore_local_overrides: false)
312
347
  res = Statsig::ConfigResult.new(
313
348
  name: config_name,
314
349
  disable_exposures: disable_log_exposure,
315
350
  disable_evaluation_details: disable_evaluation_details
316
351
  )
317
- @evaluator.get_config(user, config_name, res, user_persisted_values: user_persisted_values)
352
+ @evaluator.get_config(user, config_name, res, user_persisted_values: user_persisted_values, ignore_local_overrides: ignore_local_overrides)
318
353
 
319
354
  unless disable_log_exposure
320
355
  @logger.log_config_exposure(user, res.name, res.rule_id, res.secondary_exposures, res.evaluation_details)
@@ -336,7 +371,7 @@ class StatsigDriver
336
371
  end
337
372
 
338
373
  def normalize_user(user)
339
- if !@options&.environment.nil?
374
+ if user.statsig_environment.nil? && !@options&.environment.nil?
340
375
  user.statsig_environment = @options.environment
341
376
  end
342
377
  user
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.32.0
4
+ version: 1.33.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Statsig, Inc
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-01-26 00:00:00.000000000 Z
11
+ date: 2024-02-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -94,20 +94,6 @@ dependencies:
94
94
  - - "~>"
95
95
  - !ruby/object:Gem::Version
96
96
  version: '1.0'
97
- - !ruby/object:Gem::Dependency
98
- name: sorbet
99
- requirement: !ruby/object:Gem::Requirement
100
- requirements:
101
- - - '='
102
- - !ruby/object:Gem::Version
103
- version: 0.5.10461
104
- type: :development
105
- prerelease: false
106
- version_requirements: !ruby/object:Gem::Requirement
107
- requirements:
108
- - - '='
109
- - !ruby/object:Gem::Version
110
- version: 0.5.10461
111
97
  - !ruby/object:Gem::Dependency
112
98
  name: tapioca
113
99
  requirement: !ruby/object:Gem::Requirement
@@ -288,20 +274,6 @@ dependencies:
288
274
  - - "~>"
289
275
  - !ruby/object:Gem::Version
290
276
  version: 0.2.1
291
- - !ruby/object:Gem::Dependency
292
- name: sorbet-runtime
293
- requirement: !ruby/object:Gem::Requirement
294
- requirements:
295
- - - "~>"
296
- - !ruby/object:Gem::Version
297
- version: 0.5.10461
298
- type: :runtime
299
- prerelease: false
300
- version_requirements: !ruby/object:Gem::Requirement
301
- requirements:
302
- - - "~>"
303
- - !ruby/object:Gem::Version
304
- version: 0.5.10461
305
277
  - !ruby/object:Gem::Dependency
306
278
  name: concurrent-ruby
307
279
  requirement: !ruby/object:Gem::Requirement