statsig 1.32.0 → 1.33.0

Sign up to get free protection for your applications and to get access to all the features.
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