statsig 1.8.4 → 1.9.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: e6b5d1c640eab6b3173784683e1e87c41c97410631aaea5e9e68306cd45df829
4
- data.tar.gz: c78cb2b73b63f496672a2486a8fb7fe212dc4192838b4cf72a5e191f9b4d065f
3
+ metadata.gz: a832901a7d272c5b27408418836ded1042f720bfc0a374489ed0a81c476a1606
4
+ data.tar.gz: c89426f15ab949980299bfc21931466d076e8b23fbe26ddc290e13d1d92cb948
5
5
  SHA512:
6
- metadata.gz: 2b4601bb00d1287c24aceca4e85d9e4c786225d6c42a8abb8bd70ab687d6d0ce508e3e762b302306617d3f17e81d693293509aa1f1550ab3ad670c4bca464737
7
- data.tar.gz: d5518fc18cd937879281663b1592e286fbe45cd2a222e5ef6ab7659443fb5f5db4875ba7f29d7acbe28a5f81b8bec54195123617f58db09fa442b2ec224c1559
6
+ metadata.gz: c92f462bf372241bdd214a9a153b597792970b70631b8631be3df01b189953b6574016eedf437e397e07f62c8ef8f1c3e7ab7834f10080febe9843514cb059e0
7
+ data.tar.gz: 228deaad6a9c224c80321ddc6bd33ab13a4cd6eae86cc83f822f5d8325f25aaa13bfd0903369468b3664d28847b3239df27a446b32b349e5b3d987f77ef3fb74
data/lib/config_result.rb CHANGED
@@ -6,13 +6,15 @@ module Statsig
6
6
  attr_accessor :json_value
7
7
  attr_accessor :rule_id
8
8
  attr_accessor :secondary_exposures
9
+ attr_accessor :config_delegate
9
10
 
10
- def initialize(name, gate_value = false, json_value = {}, rule_id = '', secondary_exposures = [])
11
+ def initialize(name, gate_value = false, json_value = {}, rule_id = '', secondary_exposures = [], config_delegate = '')
11
12
  @name = name
12
13
  @gate_value = gate_value
13
14
  @json_value = json_value
14
15
  @rule_id = rule_id
15
16
  @secondary_exposures = secondary_exposures.is_a?(Array) ? secondary_exposures : []
17
+ @config_delegate = config_delegate
16
18
  end
17
19
  end
18
20
  end
data/lib/evaluator.rb CHANGED
@@ -29,6 +29,11 @@ module Statsig
29
29
  eval_spec(user, @spec_store.get_config(config_name))
30
30
  end
31
31
 
32
+ def get_layer(user, layer_name)
33
+ return nil unless @initialized && @spec_store.has_layer?(layer_name)
34
+ eval_spec(user, @spec_store.get_layer(layer_name))
35
+ end
36
+
32
37
  def shutdown
33
38
  @spec_store.shutdown
34
39
  end
@@ -44,15 +49,16 @@ module Statsig
44
49
  rule = config['rules'][i]
45
50
  result = eval_rule(user, rule)
46
51
  return $fetch_from_server if result == $fetch_from_server
47
- exposures = exposures + result['exposures'] if result['exposures'].is_a? Array
48
- if result['value']
52
+ exposures = exposures + result.secondary_exposures
53
+ if result.gate_value
49
54
  pass = eval_pass_percent(user, rule, config['salt'])
50
55
  return Statsig::ConfigResult.new(
51
56
  config['name'],
52
57
  pass,
53
- pass ? rule['returnValue'] : config['defaultValue'],
54
- rule['id'],
55
- exposures
58
+ pass ? result.json_value : config['defaultValue'],
59
+ result.rule_id,
60
+ exposures,
61
+ result.config_delegate
56
62
  )
57
63
  end
58
64
 
@@ -83,7 +89,16 @@ module Statsig
83
89
  end
84
90
  i += 1
85
91
  end
86
- { 'value' => pass, 'exposures' => exposures }
92
+
93
+ delegate = rule['configDelegate']
94
+ if pass and @spec_store.get_config(delegate)
95
+ delegated_result = self.eval_spec(user, @spec_store.get_config(delegate))
96
+ delegated_result.config_delegate = delegate
97
+ delegated_result.secondary_exposures = exposures + delegated_result.secondary_exposures
98
+ return delegated_result
99
+ end
100
+
101
+ Statsig::ConfigResult.new('', pass, rule['returnValue'], rule['id'], exposures)
87
102
  end
88
103
 
89
104
  def eval_condition(user, condition)
data/lib/layer.rb ADDED
@@ -0,0 +1,15 @@
1
+ class Layer
2
+ attr_accessor :name
3
+ attr_accessor :rule_id
4
+
5
+ def initialize(name, value = {}, rule_id = '')
6
+ @name = name
7
+ @value = value
8
+ @rule_id = rule_id
9
+ end
10
+
11
+ def get(index, default_value)
12
+ return default_value if @value.nil? || !@value.key?(index)
13
+ @value[index]
14
+ end
15
+ end
data/lib/network.rb CHANGED
@@ -1,6 +1,5 @@
1
1
  require 'http'
2
2
  require 'json'
3
- require 'dynamic_config'
4
3
 
5
4
  $retry_codes = [408, 500, 502, 503, 504, 522, 524, 599]
6
5
 
data/lib/spec_store.rb CHANGED
@@ -11,6 +11,7 @@ module Statsig
11
11
  @store = {
12
12
  :gates => {},
13
13
  :configs => {},
14
+ :layers => {},
14
15
  :id_lists => {},
15
16
  }
16
17
  e = download_config_specs
@@ -34,6 +35,10 @@ module Statsig
34
35
  @store[:configs].key?(config_name)
35
36
  end
36
37
 
38
+ def has_layer?(layer_name)
39
+ @store[:layers].key?(layer_name)
40
+ end
41
+
37
42
  def get_gate(gate_name)
38
43
  return nil unless has_gate?(gate_name)
39
44
  @store[:gates][gate_name]
@@ -44,6 +49,11 @@ module Statsig
44
49
  @store[:configs][config_name]
45
50
  end
46
51
 
52
+ def get_layer(layer_name)
53
+ return nil unless has_layer?(layer_name)
54
+ @store[:layers][layer_name]
55
+ end
56
+
47
57
  def get_id_list(list_name)
48
58
  @store[:id_lists][list_name]
49
59
  end
@@ -89,15 +99,19 @@ module Statsig
89
99
  @last_sync_time = specs_json['time'] || @last_sync_time
90
100
  return unless specs_json['has_updates'] == true &&
91
101
  !specs_json['feature_gates'].nil? &&
92
- !specs_json['dynamic_configs'].nil?
102
+ !specs_json['dynamic_configs'].nil? &&
103
+ !specs_json['layer_configs'].nil?
93
104
 
94
105
  new_gates = {}
95
106
  new_configs = {}
107
+ new_layers = {}
96
108
 
97
109
  specs_json['feature_gates'].map{|gate| new_gates[gate['name']] = gate }
98
110
  specs_json['dynamic_configs'].map{|config| new_configs[config['name']] = config }
111
+ specs_json['layer_configs'].map{|layer| new_layers[layer['name']] = layer }
99
112
  @store[:gates] = new_gates
100
113
  @store[:configs] = new_configs
114
+ @store[:layers] = new_layers
101
115
 
102
116
  new_id_lists = specs_json['id_lists']
103
117
  if new_id_lists.is_a? Hash
data/lib/statsig.rb CHANGED
@@ -25,6 +25,11 @@ module Statsig
25
25
  @shared_instance&.get_config(user, experiment_name)
26
26
  end
27
27
 
28
+ def self.get_layer(user, layer_name)
29
+ ensure_initialized
30
+ @shared_instance&.get_config(user, layer_name)
31
+ end
32
+
28
33
  def self.log_event(user, event_name, value, metadata)
29
34
  ensure_initialized
30
35
  @shared_instance&.log_event(user, event_name, value, metadata)
@@ -40,7 +45,7 @@ module Statsig
40
45
  def self.get_statsig_metadata
41
46
  {
42
47
  'sdkType' => 'ruby-server',
43
- 'sdkVersion' => '1.8.4',
48
+ 'sdkVersion' => '1.9.0',
44
49
  }
45
50
  end
46
51
 
@@ -26,12 +26,7 @@ class StatsigDriver
26
26
  end
27
27
 
28
28
  def check_gate(user, gate_name)
29
- validate_user(user)
30
- user = normalize_user(user)
31
- if !gate_name.is_a?(String) || gate_name.empty?
32
- raise 'Invalid gate_name provided'
33
- end
34
- check_shutdown
29
+ user = verify_inputs(user, gate_name, "gate_name")
35
30
 
36
31
  res = @evaluator.check_gate(user, gate_name)
37
32
  if res.nil?
@@ -49,33 +44,34 @@ class StatsigDriver
49
44
  end
50
45
 
51
46
  def get_config(user, dynamic_config_name)
52
- validate_user(user)
53
- user = normalize_user(user)
54
- if !dynamic_config_name.is_a?(String) || dynamic_config_name.empty?
55
- raise "Invalid dynamic_config_name provided"
56
- end
57
- check_shutdown
47
+ user = verify_inputs(user, dynamic_config_name, "dynamic_config_name")
48
+ get_config_impl(user, dynamic_config_name)
49
+ end
58
50
 
59
- res = @evaluator.get_config(user, dynamic_config_name)
51
+ def get_experiment(user, experiment_name)
52
+ user = verify_inputs(user, experiment_name, "experiment_name")
53
+ get_config_impl(user, experiment_name)
54
+ end
55
+
56
+ def get_layer(user, layer_name)
57
+ user = verify_inputs(user, layer_name, "layer_name")
58
+
59
+ res = @evaluator.get_layer(user, layer_name)
60
60
  if res.nil?
61
- res = Statsig::ConfigResult.new(dynamic_config_name)
61
+ res = Statsig::ConfigResult.new(layer_name)
62
62
  end
63
63
 
64
64
  if res == $fetch_from_server
65
- res = get_config_fallback(user, dynamic_config_name)
65
+ if res.config_delegate.empty?
66
+ return Layer.new(layer_name)
67
+ end
68
+ res = get_config_fallback(user, res.config_delegate)
66
69
  # exposure logged by the server
67
70
  else
68
- @logger.log_config_exposure(user, res.name, res.rule_id, res.secondary_exposures)
71
+ @logger.log_layer_exposure(user, res.name, res.rule_id, res.secondary_exposures, res.config_delegate)
69
72
  end
70
73
 
71
- DynamicConfig.new(res.name, res.json_value, res.rule_id)
72
- end
73
-
74
- def get_experiment(user, experiment_name)
75
- if !experiment_name.is_a?(String) || experiment_name.empty?
76
- raise "Invalid experiment_name provided"
77
- end
78
- get_config(user, experiment_name)
74
+ Layer.new(res.name, res.json_value, res.rule_id)
79
75
  end
80
76
 
81
77
  def log_event(user, event_name, value = nil, metadata = nil)
@@ -102,6 +98,32 @@ class StatsigDriver
102
98
 
103
99
  private
104
100
 
101
+ def verify_inputs(user, config_name, variable_name)
102
+ validate_user(user)
103
+ if !config_name.is_a?(String) || config_name.empty?
104
+ raise "Invalid " + variable_name +" provided"
105
+ end
106
+
107
+ check_shutdown
108
+ normalize_user(user)
109
+ end
110
+
111
+ def get_config_impl(user, config_name)
112
+ res = @evaluator.get_config(user, config_name)
113
+ if res.nil?
114
+ res = Statsig::ConfigResult.new(config_name)
115
+ end
116
+
117
+ if res == $fetch_from_server
118
+ res = get_config_fallback(user, config_name)
119
+ # exposure logged by the server
120
+ else
121
+ @logger.log_config_exposure(user, res.name, res.rule_id, res.secondary_exposures)
122
+ end
123
+
124
+ DynamicConfig.new(res.name, res.json_value, res.rule_id)
125
+ end
126
+
105
127
  def validate_user(user)
106
128
  if user.nil? || !user.instance_of?(StatsigUser) || !user.user_id.is_a?(String)
107
129
  raise 'Must provide a valid StatsigUser with a user_id to use the server SDK. See https://docs.statsig.com/messages/serverRequiredUserID/ for more details.'
@@ -2,6 +2,7 @@ require 'statsig_event'
2
2
 
3
3
  $gate_exposure_event = 'statsig::gate_exposure'
4
4
  $config_exposure_event = 'statsig::config_exposure'
5
+ $layer_exposure_event = 'statsig::layer_exposure'
5
6
 
6
7
  module Statsig
7
8
  class StatsigLogger
@@ -43,6 +44,19 @@ module Statsig
43
44
  log_event(event)
44
45
  end
45
46
 
47
+ def log_layer_exposure(user, config_name, rule_id, secondary_exposures, allocated_experiment)
48
+ event = StatsigEvent.new($layer_exposure_event)
49
+ event.user = user
50
+ event.metadata = {
51
+ 'config' => config_name,
52
+ 'ruleID' => rule_id,
53
+ 'allocatedExperiment' => allocated_experiment
54
+ }
55
+ event.statsig_metadata = Statsig.get_statsig_metadata
56
+ event.secondary_exposures = secondary_exposures.is_a?(Array) ? secondary_exposures : []
57
+ log_event(event)
58
+ end
59
+
46
60
  def periodic_flush
47
61
  Thread.new do
48
62
  loop do
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.8.4
4
+ version: 1.9.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-02-11 00:00:00.000000000 Z
11
+ date: 2022-03-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -118,6 +118,7 @@ files:
118
118
  - lib/dynamic_config.rb
119
119
  - lib/evaluation_helpers.rb
120
120
  - lib/evaluator.rb
121
+ - lib/layer.rb
121
122
  - lib/network.rb
122
123
  - lib/spec_store.rb
123
124
  - lib/statsig.rb
@@ -145,7 +146,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
145
146
  - !ruby/object:Gem::Version
146
147
  version: '0'
147
148
  requirements: []
148
- rubygems_version: 3.2.3
149
+ rubygems_version: 3.3.7
149
150
  signing_key:
150
151
  specification_version: 4
151
152
  summary: Statsig server SDK for Ruby