statsig 1.16.2 → 1.17.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: 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