statsig 1.16.2 → 1.17.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: 980b704314aa26f9167bcf3fa86ba20d8ee0092232118ce4c43c9df53b839e90
4
- data.tar.gz: 7b47108f78e72f85b1ad1386ca5e45b080178f8c6ab4e51d390b8778ad941a67
3
+ metadata.gz: 5b81f929cdedefaee6f57cb2b0f7aec05bbfd28cf6b4f273fc4fb3bbcf46858f
4
+ data.tar.gz: 5caa719d8a301cbcd95d68f9bc5802a89f0691600c7b5883bfdbfcc8fa21bb8f
5
5
  SHA512:
6
- metadata.gz: a511d07303530dee09538427b04dd0932695a54b4241c6b1c3989c58dff3b1c3ec74a28bdbe89e7849aaebf1f489c7c68de34f0008d3a8891103e0f8b3dec2ea
7
- data.tar.gz: '0694f51e7735bc6ce83f8244b7a6bcccf70d822e28b0a7051326973351eaa8db98daa1637146ada8c467b8908e94c1239aecf5fc0b703462a1b01eac94534b3b'
6
+ metadata.gz: 9372cd1b25a0b91054d6315df027833cd2377e51e1f76385dd260fe9a17665da33330c357cf63756f6bc6701e51f76a7d5b81aa99d57a96bfeee807c575c3b19
7
+ data.tar.gz: 5dd1c6ad849e20c0ed15b451371a78e971f6da4ed83f7bf7f79eb5db65c88ba27f1b8e9da991587d8a46cbd2086d0b30692bbb81e48283e758b84d679a63eb3a
@@ -1,3 +1,4 @@
1
+ # typed: true
1
2
  $empty_eval_result = {
2
3
  :gate_value => false,
3
4
  :json_value => {},
data/lib/config_result.rb CHANGED
@@ -1,3 +1,4 @@
1
+ # typed: true
1
2
  module Statsig
2
3
  class ConfigResult
3
4
  attr_accessor :name
@@ -1,19 +1,50 @@
1
+ # typed: false
2
+
3
+ require 'sorbet-runtime'
4
+
5
+ ##
6
+ # Contains the current experiment/dynamic config values from Statsig
7
+ #
8
+ # Dynamic Config Documentation: https://docs.statsig.com/dynamic-config
9
+ #
10
+ # Experiments Documentation: https://docs.statsig.com/experiments-plus
1
11
  class DynamicConfig
12
+ extend T::Sig
13
+
14
+ sig { returns(String) }
2
15
  attr_accessor :name
16
+
17
+ sig { returns(T::Hash[String, T.untyped]) }
3
18
  attr_accessor :value
19
+
20
+ sig { returns(String) }
4
21
  attr_accessor :rule_id
5
22
 
23
+ sig { params(name: String, value: T::Hash[String, T.untyped], rule_id: String).void }
6
24
  def initialize(name, value = {}, rule_id = '')
7
25
  @name = name
8
26
  @value = value
9
27
  @rule_id = rule_id
10
28
  end
11
29
 
30
+ sig { params(index: String, default_value: T.untyped).returns(T.untyped) }
31
+ ##
32
+ # Get the value for the given key (index), falling back to the default_value if it cannot be found.
33
+ #
34
+ # @param index The name of parameter being fetched
35
+ # @param default_value The fallback value if the name cannot be found
12
36
  def get(index, default_value)
13
37
  return default_value if @value.nil? || !@value.key?(index)
14
38
  @value[index]
15
39
  end
16
40
 
41
+ sig { params(index: String, default_value: T.untyped).returns(T.untyped) }
42
+ ##
43
+ # Get the value for the given key (index), falling back to the default_value if it cannot be found
44
+ # or is found to have a different type from the default_value.
45
+ #
46
+ # @param index The name of parameter being fetched
47
+ # @param default_value The fallback value if the name cannot be found
17
48
  def get_typed(index, default_value)
18
49
  return default_value if @value.nil? || !@value.key?(index)
19
50
  return default_value if @value[index].class != default_value.class and default_value.class != TrueClass and default_value.class != FalseClass
@@ -1,3 +1,4 @@
1
+ # typed: true
1
2
  module Statsig
2
3
 
3
4
  module EvaluationReason
@@ -1,3 +1,4 @@
1
+ # typed: true
1
2
  require 'time'
2
3
 
3
4
  module EvaluationHelpers
data/lib/evaluator.rb CHANGED
@@ -1,3 +1,4 @@
1
+ # typed: false
1
2
  require 'config_result'
2
3
  require 'country_lookup'
3
4
  require 'digest'
@@ -378,14 +379,14 @@ module Statsig
378
379
  user_custom = user_lookup_table['custom']
379
380
  if user_custom.is_a?(Hash)
380
381
  user_custom.each do |key, value|
381
- return value if key.downcase.casecmp?(field.downcase) && !value.nil?
382
+ return value if key.to_s.downcase.casecmp?(field.downcase) && !value.nil?
382
383
  end
383
384
  end
384
385
 
385
386
  private_attributes = user_lookup_table['privateAttributes']
386
387
  if private_attributes.is_a?(Hash)
387
388
  private_attributes.each do |key, value|
388
- return value if key.downcase.casecmp?(field.downcase) && !value.nil?
389
+ return value if key.to_s.downcase.casecmp?(field.downcase) && !value.nil?
389
390
  end
390
391
  end
391
392
 
@@ -397,7 +398,7 @@ module Statsig
397
398
  field = field.downcase
398
399
  return nil unless user.statsig_environment.is_a? Hash
399
400
  user.statsig_environment.each do |key, value|
400
- return value if key.downcase == (field)
401
+ return value if key.to_s.downcase == (field)
401
402
  end
402
403
  nil
403
404
  end
data/lib/id_list.rb CHANGED
@@ -1,3 +1,4 @@
1
+ # typed: true
1
2
  module Statsig
2
3
  class IDList
3
4
  attr_accessor :name
@@ -1,3 +1,4 @@
1
+ # typed: true
1
2
  module Statsig
2
3
  module Interfaces
3
4
  class IDataStore
data/lib/layer.rb CHANGED
@@ -1,7 +1,22 @@
1
+ # typed: false
2
+
3
+ ##
4
+ # Contains the current values from Statsig.
5
+ # Will contain layer default values for all shared parameters in that layer.
6
+ # If a parameter is in an active experiment, and the current user is allocated to that experiment,
7
+ # those parameters will be updated to reflect the experiment values not the layer defaults.
8
+ #
9
+ # Layers Documentation: https://docs.statsig.com/layers
1
10
  class Layer
11
+ extend T::Sig
12
+
13
+ sig { returns(String) }
2
14
  attr_accessor :name
15
+
16
+ sig { returns(String) }
3
17
  attr_accessor :rule_id
4
18
 
19
+ sig { params(name: String, value: T::Hash[String, T.untyped], rule_id: String, exposure_log_func: T.any(Method, Proc, NilClass)).void }
5
20
  def initialize(name, value = {}, rule_id = '', exposure_log_func = nil)
6
21
  @name = name
7
22
  @value = value
@@ -9,6 +24,12 @@ class Layer
9
24
  @exposure_log_func = exposure_log_func
10
25
  end
11
26
 
27
+ sig { params(index: String, default_value: T.untyped).returns(T.untyped) }
28
+ ##
29
+ # Get the value for the given key (index), falling back to the default_value if it cannot be found.
30
+ #
31
+ # @param index The name of parameter being fetched
32
+ # @param default_value The fallback value if the name cannot be found
12
33
  def get(index, default_value)
13
34
  return default_value if @value.nil? || !@value.key?(index)
14
35
 
@@ -19,6 +40,13 @@ class Layer
19
40
  @value[index]
20
41
  end
21
42
 
43
+ sig { params(index: String, default_value: T.untyped).returns(T.untyped) }
44
+ ##
45
+ # Get the value for the given key (index), falling back to the default_value if it cannot be found
46
+ # or is found to have a different type from the default_value.
47
+ #
48
+ # @param index The name of parameter being fetched
49
+ # @param default_value The fallback value if the name cannot be found
22
50
  def get_typed(index, default_value)
23
51
  return default_value if @value.nil? || !@value.key?(index)
24
52
  return default_value if @value[index].class != default_value.class and default_value.class != TrueClass and default_value.class != FalseClass
data/lib/network.rb CHANGED
@@ -1,11 +1,18 @@
1
+ # typed: true
2
+
1
3
  require 'http'
2
4
  require 'json'
3
5
  require 'securerandom'
6
+ require 'sorbet-runtime'
4
7
 
5
8
  $retry_codes = [408, 500, 502, 503, 504, 522, 524, 599]
6
9
 
7
10
  module Statsig
8
11
  class Network
12
+ extend T::Sig
13
+
14
+ sig { params(server_secret: String, api: String, local_mode: T::Boolean, backoff_mult: Integer).void }
15
+
9
16
  def initialize(server_secret, api, local_mode, backoff_mult = 10)
10
17
  super()
11
18
  unless api.end_with?('/')
@@ -18,6 +25,10 @@ module Statsig
18
25
  @session_id = SecureRandom.uuid
19
26
  end
20
27
 
28
+
29
+ sig { params(endpoint: String, body: String, retries: Integer, backoff: Integer)
30
+ .returns([T.any(HTTP::Response, NilClass), T.any(StandardError, NilClass)]) }
31
+
21
32
  def post_helper(endpoint, body, retries = 0, backoff = 1)
22
33
  if @local_mode
23
34
  return nil, nil
data/lib/spec_store.rb CHANGED
@@ -1,7 +1,9 @@
1
+ # typed: false
1
2
  require 'net/http'
2
3
  require 'uri'
3
4
  require 'evaluation_details'
4
5
  require 'id_list'
6
+ require 'concurrent-ruby'
5
7
 
6
8
  module Statsig
7
9
  class SpecStore
@@ -30,6 +32,12 @@ module Statsig
30
32
  :experiment_to_layer => {}
31
33
  }
32
34
 
35
+ @id_list_thread_pool = Concurrent::FixedThreadPool.new(
36
+ options.idlist_threadpool_size,
37
+ max_queue: 100,
38
+ fallback_policy: :discard,
39
+ )
40
+
33
41
  unless @options.bootstrap_values.nil?
34
42
  begin
35
43
  if !@options.data_store.nil?
@@ -62,6 +70,8 @@ module Statsig
62
70
  def shutdown
63
71
  @config_sync_thread&.exit
64
72
  @id_lists_sync_thread&.exit
73
+ @id_list_thread_pool.shutdown
74
+ @id_list_thread_pool.wait_for_termination(timeout = 3)
65
75
  unless @options.data_store.nil?
66
76
  @options.data_store.shutdown
67
77
  end
@@ -221,7 +231,7 @@ module Statsig
221
231
  if !server_id_lists.is_a?(Hash) || !local_id_lists.is_a?(Hash)
222
232
  return
223
233
  end
224
- threads = []
234
+ tasks = []
225
235
 
226
236
  server_id_lists.each do |list_name, list|
227
237
  server_list = IDList.new(list)
@@ -250,11 +260,16 @@ module Statsig
250
260
  next
251
261
  end
252
262
 
253
- threads << Thread.new do
263
+ tasks << Concurrent::Promise.execute(:executor => @id_list_thread_pool) do
254
264
  download_single_id_list(local_list)
255
265
  end
256
266
  end
257
- threads.each(&:join)
267
+
268
+ result = Concurrent::Promise.all?(*tasks).execute.wait(@id_lists_sync_interval)
269
+ if result.state != :fulfilled
270
+ return # timed out
271
+ end
272
+
258
273
  delete_lists = []
259
274
  local_id_lists.each do |list_name, list|
260
275
  unless server_id_lists.key? list_name
data/lib/statsig.rb CHANGED
@@ -1,6 +1,19 @@
1
+ # typed: true
2
+
1
3
  require 'statsig_driver'
4
+ require 'sorbet-runtime'
2
5
 
3
6
  module Statsig
7
+ extend T::Sig
8
+
9
+
10
+ sig { params(secret_key: String, options: T.any(StatsigOptions, NilClass), error_callback: T.any(Method, Proc, NilClass)).void }
11
+ ##
12
+ # Initializes the Statsig SDK.
13
+ #
14
+ # @param secret_key The server SDK key copied from console.statsig.com
15
+ # @param options The StatsigOptions object used to configure the SDK
16
+ # @param error_callback A callback function, called if the initialize network call fails
4
17
  def self.initialize(secret_key, options = nil, error_callback = nil)
5
18
  unless @shared_instance.nil?
6
19
  puts 'Statsig already initialized.'
@@ -11,31 +24,70 @@ module Statsig
11
24
  @shared_instance = StatsigDriver.new(secret_key, options, error_callback)
12
25
  end
13
26
 
27
+ sig { params(user: StatsigUser, gate_name: String).returns(T::Boolean) }
28
+ ##
29
+ # Gets the boolean result of a gate, evaluated against the given user. An exposure event will automatically be logged for the gate.
30
+ #
31
+ # @param user A StatsigUser object used for the evaluation
32
+ # @param gate_name The name of the gate being checked
14
33
  def self.check_gate(user, gate_name)
15
34
  ensure_initialized
16
35
  @shared_instance&.check_gate(user, gate_name)
17
36
  end
18
37
 
38
+ sig { params(user: StatsigUser, dynamic_config_name: String).returns(DynamicConfig) }
39
+ ##
40
+ # Get the values of a dynamic config, evaluated against the given user. An exposure event will automatically be logged for the dynamic config.
41
+ #
42
+ # @param user A StatsigUser object used for the evaluation
43
+ # @param dynamic_config_name The name of the dynamic config
19
44
  def self.get_config(user, dynamic_config_name)
20
45
  ensure_initialized
21
46
  @shared_instance&.get_config(user, dynamic_config_name)
22
47
  end
23
48
 
49
+ sig { params(user: StatsigUser, experiment_name: String).returns(DynamicConfig) }
50
+ ##
51
+ # Get the values of an experiment, evaluated against the given user. An exposure event will automatically be logged for the experiment.
52
+ #
53
+ # @param user A StatsigUser object used for the evaluation
54
+ # @param experiment_name The name of the experiment
24
55
  def self.get_experiment(user, experiment_name)
25
56
  ensure_initialized
26
57
  @shared_instance&.get_experiment(user, experiment_name)
27
58
  end
28
59
 
60
+ sig { params(user: StatsigUser, layer_name: String).returns(Layer) }
61
+ ##
62
+ # Get the values of a layer, evaluated against the given user.
63
+ # Exposure events will be fired when get or get_typed is called on the resulting Layer class.
64
+ #
65
+ # @param user A StatsigUser object used for the evaluation
66
+ # @param layer_name The name of the layer
29
67
  def self.get_layer(user, layer_name)
30
68
  ensure_initialized
31
69
  @shared_instance&.get_layer(user, layer_name)
32
70
  end
33
71
 
72
+ sig { params(user: StatsigUser,
73
+ event_name: String,
74
+ value: T.any(String, Integer, Float, NilClass),
75
+ metadata: T.any(T::Hash[String, T.untyped], NilClass)).void }
76
+ ##
77
+ # Logs an event to Statsig with the provided values.
78
+ #
79
+ # @param user A StatsigUser object to be included in the log
80
+ # @param event_name The name given to the event
81
+ # @param value A top level value for the event
82
+ # @param metadata Any extra values to be logged
34
83
  def self.log_event(user, event_name, value = nil, metadata = nil)
35
84
  ensure_initialized
36
85
  @shared_instance&.log_event(user, event_name, value, metadata)
37
86
  end
38
87
 
88
+ sig { void }
89
+ ##
90
+ # Stops all Statsig activity and flushes any pending events.
39
91
  def self.shutdown
40
92
  unless @shared_instance.nil?
41
93
  @shared_instance.shutdown
@@ -43,25 +95,48 @@ module Statsig
43
95
  @shared_instance = nil
44
96
  end
45
97
 
98
+ sig { params(gate_name: String, gate_value: T::Boolean).void }
99
+ ##
100
+ # Sets a value to be returned for the given gate instead of the actual evaluated value.
101
+ #
102
+ # @param gate_name The name of the gate to be overridden
103
+ # @param gate_value The value that will be returned
46
104
  def self.override_gate(gate_name, gate_value)
47
105
  ensure_initialized
48
106
  @shared_instance&.override_gate(gate_name, gate_value)
49
107
  end
50
108
 
109
+ sig { params(config_name: String, config_value: Hash).void }
110
+ ##
111
+ # Sets a value to be returned for the given dynamic config/experiment instead of the actual evaluated value.
112
+ #
113
+ # @param config_name The name of the dynamic config or experiment to be overridden
114
+ # @param config_value The value that will be returned
51
115
  def self.override_config(config_name, config_value)
52
116
  ensure_initialized
53
117
  @shared_instance&.override_config(config_name, config_value)
54
118
  end
55
119
 
120
+ sig { params(user: StatsigUser).returns(T.any(T::Hash[String, T.untyped], NilClass)) }
121
+ ##
122
+ # Gets all evaluated values for the given user.
123
+ # These values can then be given to a Statsig Client SDK via bootstrapping.
124
+ #
125
+ # @param user A StatsigUser object used for the evaluation
126
+ #
127
+ # @note See Ruby Documentation: https://docs.statsig.com/server/rubySDK)
56
128
  def self.get_client_initialize_response(user)
57
129
  ensure_initialized
58
130
  @shared_instance&.get_client_initialize_response(user)
59
131
  end
60
132
 
133
+ sig { returns(T::Hash[String, String]) }
134
+ ##
135
+ # Internal Statsig metadata for this SDK
61
136
  def self.get_statsig_metadata
62
137
  {
63
138
  'sdkType' => 'ruby-server',
64
- 'sdkVersion' => '1.16.2',
139
+ 'sdkVersion' => '1.17.0',
65
140
  }
66
141
  end
67
142
 
@@ -72,4 +147,21 @@ module Statsig
72
147
  raise 'Must call initialize first.'
73
148
  end
74
149
  end
150
+
151
+ T::Configuration.call_validation_error_handler = lambda do |signature, opts|
152
+ puts opts[:pretty_message]
153
+ end
154
+
155
+ T::Configuration.inline_type_error_handler = lambda do |error, opts|
156
+ puts error.message
157
+ end
158
+
159
+ T::Configuration.sig_builder_error_handler = lambda do |error, location|
160
+ puts error.message
161
+ end
162
+
163
+ T::Configuration.sig_validation_error_handler = lambda do |error, opts|
164
+ puts error.message
165
+ end
166
+
75
167
  end
@@ -1,3 +1,5 @@
1
+ # typed: true
2
+
1
3
  require 'config_result'
2
4
  require 'evaluator'
3
5
  require 'network'
@@ -8,11 +10,15 @@ require 'statsig_user'
8
10
  require 'spec_store'
9
11
  require 'dynamic_config'
10
12
  require 'layer'
13
+ require 'sorbet-runtime'
11
14
 
12
15
  class StatsigDriver
16
+ extend T::Sig
17
+
18
+ sig { params(secret_key: String, options: T.any(StatsigOptions, NilClass), error_callback: T.any(Method, Proc, NilClass)).void }
19
+
13
20
  def initialize(secret_key, options = nil, error_callback = nil)
14
- super()
15
- if !secret_key.is_a?(String) || !secret_key.start_with?('secret-')
21
+ unless secret_key.start_with?('secret-')
16
22
  raise 'Invalid secret key provided. Provide your project secret key from the Statsig console'
17
23
  end
18
24
  if !options.nil? && !options.instance_of?(StatsigOptions)
@@ -27,6 +33,8 @@ class StatsigDriver
27
33
  @evaluator = Statsig::Evaluator.new(@net, @options, error_callback)
28
34
  end
29
35
 
36
+ sig { params(user: StatsigUser, gate_name: String).returns(T::Boolean) }
37
+
30
38
  def check_gate(user, gate_name)
31
39
  user = verify_inputs(user, gate_name, "gate_name")
32
40
 
@@ -45,16 +53,22 @@ class StatsigDriver
45
53
  res.gate_value
46
54
  end
47
55
 
56
+ sig { params(user: StatsigUser, dynamic_config_name: String).returns(DynamicConfig) }
57
+
48
58
  def get_config(user, dynamic_config_name)
49
59
  user = verify_inputs(user, dynamic_config_name, "dynamic_config_name")
50
60
  get_config_impl(user, dynamic_config_name)
51
61
  end
52
62
 
63
+ sig { params(user: StatsigUser, experiment_name: String).returns(DynamicConfig) }
64
+
53
65
  def get_experiment(user, experiment_name)
54
66
  user = verify_inputs(user, experiment_name, "experiment_name")
55
67
  get_config_impl(user, experiment_name)
56
68
  end
57
69
 
70
+ sig { params(user: StatsigUser, layer_name: String).returns(Layer) }
71
+
58
72
  def get_layer(user, layer_name)
59
73
  user = verify_inputs(user, layer_name, "layer_name")
60
74
 
@@ -115,6 +129,7 @@ class StatsigDriver
115
129
 
116
130
  def maybe_restart_background_threads
117
131
  @evaluator.maybe_restart_background_threads
132
+ @logger.maybe_restart_background_threads
118
133
  end
119
134
 
120
135
  private
data/lib/statsig_event.rb CHANGED
@@ -1,3 +1,4 @@
1
+ # typed: true
1
2
  class StatsigEvent
2
3
  attr_accessor :value
3
4
  attr_accessor :metadata
@@ -1,3 +1,4 @@
1
+ # typed: true
1
2
  require 'statsig_event'
2
3
  require 'concurrent-ruby'
3
4
 
@@ -117,6 +118,12 @@ module Statsig
117
118
  @network.post_logs(flush_events)
118
119
  end
119
120
 
121
+ def maybe_restart_background_threads
122
+ if @background_flush.nil? or !@background_flush.alive?
123
+ @background_flush = periodic_flush
124
+ end
125
+ end
126
+
120
127
  private
121
128
 
122
129
  def safe_add_eval_details(eval_details, event)
@@ -1,18 +1,88 @@
1
+ # typed: true
2
+
3
+ require 'sorbet-runtime'
4
+ require_relative 'interfaces/data_store'
5
+
6
+ ##
7
+ # Configuration options for the Statsig SDK.
1
8
  class StatsigOptions
9
+ extend T::Sig
10
+
11
+ sig { returns(T.any(T::Hash[String, String], NilClass)) }
12
+ # Hash you can use to set environment variables that apply to all of your users in
13
+ # the same session and will be used for targeting purposes.
14
+ # eg. { "tier" => "development" }
2
15
  attr_accessor :environment
16
+
17
+ sig { returns(String) }
18
+ # The base url used to make network calls to Statsig.
19
+ # default: https://statsigapi.net/v1
3
20
  attr_accessor :api_url_base
21
+
22
+ sig { returns(T.any(Float, Integer)) }
23
+ # The interval (in seconds) to poll for changes to your Statsig configuration
24
+ # default: 10s
4
25
  attr_accessor :rulesets_sync_interval
26
+
27
+ sig { returns(T.any(Float, Integer)) }
28
+ # The interval (in seconds) to poll for changes to your id lists
29
+ # default: 60s
5
30
  attr_accessor :idlists_sync_interval
31
+
32
+ sig { returns(T.any(Float, Integer)) }
33
+ # How often to flush logs to Statsig
34
+ # default: 60s
6
35
  attr_accessor :logging_interval_seconds
36
+
37
+ sig { returns(Integer) }
38
+ # The maximum number of events to batch before flushing logs to the server
39
+ # default: 1000
7
40
  attr_accessor :logging_max_buffer_size
41
+
42
+ sig { returns(T::Boolean) }
43
+ # Restricts the SDK to not issue any network requests and only respond with default values (or local overrides)
44
+ # default: false
8
45
  attr_accessor :local_mode
46
+
47
+ sig { returns(T.any(String, NilClass)) }
48
+ # A string that represents all rules for all feature gates, dynamic configs and experiments.
49
+ # It can be provided to bootstrap the Statsig server SDK at initialization in case your server runs
50
+ # into network issue or Statsig is down temporarily.
9
51
  attr_accessor :bootstrap_values
52
+
53
+ sig { returns(T.any(Method, Proc, NilClass)) }
54
+ # A callback function that will be called anytime the rulesets are updated.
10
55
  attr_accessor :rules_updated_callback
56
+
57
+ sig { returns(T.any(Statsig::Interfaces::IDataStore, NilClass)) }
58
+ # A class that extends IDataStore. Can be used to provide values from a
59
+ # common data store (like Redis) to initialize the Statsig SDK.
11
60
  attr_accessor :data_store
12
61
 
62
+ sig { returns(Integer) }
63
+ # The number of threads allocated to syncing IDLists.
64
+ # default: 3
65
+ attr_accessor :idlist_threadpool_size
66
+
67
+ sig do
68
+ params(
69
+ environment: T.any(T::Hash[String, String], NilClass),
70
+ api_url_base: String,
71
+ rulesets_sync_interval: T.any(Float, Integer),
72
+ idlists_sync_interval: T.any(Float, Integer),
73
+ logging_interval_seconds: T.any(Float, Integer),
74
+ logging_max_buffer_size: Integer,
75
+ local_mode: T::Boolean,
76
+ bootstrap_values: T.any(String, NilClass),
77
+ rules_updated_callback: T.any(Method, Proc, NilClass),
78
+ data_store: T.any(Statsig::Interfaces::IDataStore, NilClass),
79
+ idlist_threadpool_size: Integer
80
+ ).void
81
+ end
82
+
13
83
  def initialize(
14
- environment=nil,
15
- api_url_base='https://statsigapi.net/v1',
84
+ environment = nil,
85
+ api_url_base = 'https://statsigapi.net/v1',
16
86
  rulesets_sync_interval: 10,
17
87
  idlists_sync_interval: 60,
18
88
  logging_interval_seconds: 60,
@@ -20,7 +90,8 @@ class StatsigOptions
20
90
  local_mode: false,
21
91
  bootstrap_values: nil,
22
92
  rules_updated_callback: nil,
23
- data_store: nil)
93
+ data_store: nil,
94
+ idlist_threadpool_size: 3)
24
95
  @environment = environment.is_a?(Hash) ? environment : nil
25
96
  @api_url_base = api_url_base
26
97
  @rulesets_sync_interval = rulesets_sync_interval
@@ -31,5 +102,6 @@ class StatsigOptions
31
102
  @bootstrap_values = bootstrap_values
32
103
  @rules_updated_callback = rules_updated_callback
33
104
  @data_store = data_store
105
+ @idlist_threadpool_size = idlist_threadpool_size
34
106
  end
35
107
  end
data/lib/statsig_user.rb CHANGED
@@ -1,50 +1,78 @@
1
+ # typed: true
2
+
3
+ require 'sorbet-runtime'
4
+
5
+ ##
6
+ # The user object to be evaluated against your Statsig configurations (gates/experiments/dynamic configs).
1
7
  class StatsigUser
8
+ extend T::Sig
9
+
10
+ sig { returns(T.any(String, NilClass)) }
11
+ # An identifier for this user. Evaluated against the User ID criteria. (https://docs.statsig.com/feature-gates/conditions#userid)
2
12
  attr_accessor :user_id
13
+
14
+ sig { returns(T.any(String, NilClass)) }
15
+ # An identifier for this user. Evaluated against the Email criteria. (https://docs.statsig.com/feature-gates/conditions#email)
3
16
  attr_accessor :email
17
+
18
+ sig { returns(T.any(String, NilClass)) }
19
+ # An IP address associated with this user. Evaluated against the IP Address criteria. (https://docs.statsig.com/feature-gates/conditions#ip)
4
20
  attr_accessor :ip
21
+
22
+ sig { returns(T.any(String, NilClass)) }
23
+ # A user agent string associated with this user. Evaluated against Browser Version and Name (https://docs.statsig.com/feature-gates/conditions#browser-version)
5
24
  attr_accessor :user_agent
25
+
26
+ sig { returns(T.any(String, NilClass)) }
27
+ # The country code associated with this user (e.g New Zealand => NZ). Evaluated against the Country criteria. (https://docs.statsig.com/feature-gates/conditions#country)
6
28
  attr_accessor :country
29
+
30
+ sig { returns(T.any(String, NilClass)) }
31
+ # An locale for this user.
7
32
  attr_accessor :locale
33
+
34
+ sig { returns(T.any(String, NilClass)) }
35
+ # The current app version the user is interacting with. Evaluated against the App Version criteria. (https://docs.statsig.com/feature-gates/conditions#app-version)
8
36
  attr_accessor :app_version
37
+
38
+ sig { returns(T.any(T::Hash[String, String], NilClass)) }
39
+ # A Hash you can use to set environment variables that apply to this user. e.g. { "tier" => "development" }
9
40
  attr_accessor :statsig_environment
41
+
42
+ sig { returns(T.any(T::Hash[String, String], NilClass)) }
43
+ # Any Custom IDs to associated with the user. (See https://docs.statsig.com/guides/experiment-on-custom-id-types)
10
44
  attr_accessor :custom_ids
45
+
46
+ sig { returns(T.any(T::Hash[String, String], NilClass)) }
47
+ # Any value you wish to use in evaluation, but do not want logged with events, can be stored in this field.
11
48
  attr_accessor :private_attributes
12
49
 
50
+ sig { returns(T.any(T::Hash[String, T.untyped], NilClass)) }
51
+
13
52
  def custom
14
53
  @custom
15
54
  end
16
55
 
56
+ sig { params(value: T.any(T::Hash[String, T.untyped], NilClass)).void }
57
+ # Any custom fields for this user. Evaluated against the Custom criteria. (https://docs.statsig.com/feature-gates/conditions#custom)
17
58
  def custom=(value)
18
59
  @custom = value.is_a?(Hash) ? value : Hash.new
19
60
  end
20
61
 
62
+ sig { params(user_hash: T.any(T::Hash[T.any(String, Symbol), T.untyped], NilClass)).void }
63
+
21
64
  def initialize(user_hash)
22
- @user_id = nil
23
- @email = nil
24
- @ip = nil
25
- @user_agent = nil
26
- @country = nil
27
- @locale = nil
28
- @app_version = nil
29
- @custom = nil
30
- @private_attributes = nil
31
- @custom_ids = nil
32
- @statsig_environment = Hash.new
33
- if user_hash.is_a?(Hash)
34
- @user_id = user_hash['userID'] || user_hash['user_id']
35
- @user_id = @user_id.to_s unless @user_id.nil?
36
- @email = user_hash['email']
37
- @ip = user_hash['ip']
38
- @user_agent = user_hash['userAgent'] || user_hash['user_agent']
39
- @country = user_hash['country']
40
- @locale = user_hash['locale']
41
- @app_version = user_hash['appVersion'] || user_hash['app_version']
42
- @custom = user_hash['custom'] if user_hash['custom'].is_a? Hash
43
- @statsig_environment = user_hash['statsigEnvironment']
44
- @private_attributes = user_hash['privateAttributes'] if user_hash['privateAttributes'].is_a? Hash
45
- custom_ids = user_hash['customIDs'] || user_hash['custom_ids']
46
- @custom_ids = custom_ids if custom_ids.is_a? Hash
47
- end
65
+ @user_id = from_hash(user_hash, [:user_id, :userID], String)
66
+ @email = from_hash(user_hash, [:email], String)
67
+ @ip = from_hash(user_hash, [:ip], String)
68
+ @user_agent = from_hash(user_hash, [:user_agent, :userAgent], String)
69
+ @country = from_hash(user_hash, [:country], String)
70
+ @locale = from_hash(user_hash, [:locale], String)
71
+ @app_version = from_hash(user_hash, [:app_version, :appVersion], String)
72
+ @custom = from_hash(user_hash, [:custom], Hash)
73
+ @private_attributes = from_hash(user_hash, [:private_attributes, :privateAttributes], Hash)
74
+ @custom_ids = from_hash(user_hash, [:custom_ids, :customIDs], Hash)
75
+ @statsig_environment = from_hash(user_hash, [:statsig_environment, :statsigEnvironment], Hash)
48
76
  end
49
77
 
50
78
  def serialize(for_logging)
@@ -86,4 +114,29 @@ class StatsigUser
86
114
  'privateAttributes' => @private_attributes,
87
115
  }
88
116
  end
117
+
118
+ private
119
+
120
+ sig {
121
+ params(user_hash: T.any(T::Hash[T.any(String, Symbol), T.untyped], NilClass),
122
+ keys: T::Array[Symbol],
123
+ type: T.untyped)
124
+ .returns(T.untyped)
125
+ }
126
+ # Pulls fields from the user hash via Symbols and Strings
127
+ def from_hash(user_hash, keys, type)
128
+ if user_hash.nil?
129
+ return nil
130
+ end
131
+
132
+ keys.each do |key|
133
+ val = user_hash[key] || user_hash[key.to_s]
134
+ if not val.nil? and val.is_a? type
135
+ return val
136
+ end
137
+ end
138
+
139
+ nil
140
+ end
141
+
89
142
  end
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.16.2
4
+ version: 1.17.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: 2022-10-20 00:00:00.000000000 Z
11
+ date: 2022-11-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -114,6 +114,20 @@ dependencies:
114
114
  - - '='
115
115
  - !ruby/object:Gem::Version
116
116
  version: 0.1.1
117
+ - !ruby/object:Gem::Dependency
118
+ name: sorbet-runtime
119
+ requirement: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - ">="
122
+ - !ruby/object:Gem::Version
123
+ version: '0'
124
+ type: :runtime
125
+ prerelease: false
126
+ version_requirements: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - ">="
129
+ - !ruby/object:Gem::Version
130
+ version: '0'
117
131
  - !ruby/object:Gem::Dependency
118
132
  name: concurrent-ruby
119
133
  requirement: !ruby/object:Gem::Requirement
@@ -170,7 +184,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
170
184
  - !ruby/object:Gem::Version
171
185
  version: '0'
172
186
  requirements: []
173
- rubygems_version: 3.3.11
187
+ rubygems_version: 3.3.22
174
188
  signing_key:
175
189
  specification_version: 4
176
190
  summary: Statsig server SDK for Ruby