statsig 1.5.1 → 1.6.3

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: a7fc845aabeb9119437279ba79db3970aecf676229ea36c0fd5af08186cb87af
4
- data.tar.gz: d81e0e6b0ae19959b95bfbd56d0dfb55c8baaab0bfa393a099e84226bb5a8fb5
3
+ metadata.gz: 9192f1c9886d3a3b18c0a2e70599e998b4784b23245e9064018ec31c98881b25
4
+ data.tar.gz: eb329d5f742ce2f77391f3f33ba4229852c2c68827643159b51fd870c1fb21d9
5
5
  SHA512:
6
- metadata.gz: 1d9d50f2f01599da2d6a5694c049e4a9ff1caca997563d69849588068e8505878678c5bbc7d8be2b7f887eb1ce36acea6e0f465246f4ce77117860ce77c34f7c
7
- data.tar.gz: e3da88258f99e6579a9b07cd0a5d715587a56dc78b0c99cc490a52d0a12962bb7a74c783458b8cf85f497cf92d74b75a5eca9ae3053a37a84cfb5609b8252067
6
+ metadata.gz: e8a090dc289fe6994b08578b2d2884f9196f37f4104120c546dd11b3036bc939b84a343a643f15f8e12ca01a494f920206ee01a2302de5bcb71a94efb3048b76
7
+ data.tar.gz: 995f91cb599608d0691e8f598d7bc2aa50f0f1b652ad1fac83f3afdb9e950132e21a34aaee44c57797849fb99005cc9dea9d4664f3f4f0ba13afb75dd120c251
data/lib/config_result.rb CHANGED
@@ -3,11 +3,13 @@ class ConfigResult
3
3
  attr_accessor :gate_value
4
4
  attr_accessor :json_value
5
5
  attr_accessor :rule_id
6
+ attr_accessor :secondary_exposures
6
7
 
7
- def initialize(name, gate_value = false, json_value = {}, rule_id = '')
8
+ def initialize(name, gate_value = false, json_value = {}, rule_id = '', secondary_exposures = [])
8
9
  @name = name
9
10
  @gate_value = gate_value
10
11
  @json_value = json_value
11
12
  @rule_id = rule_id
13
+ @secondary_exposures = secondary_exposures.is_a?(Array) ? secondary_exposures : []
12
14
  end
13
15
  end
@@ -6,19 +6,11 @@ module EvaluationHelpers
6
6
  func.call(a.to_f, b.to_f) rescue false
7
7
  end
8
8
 
9
- # returns true if array contains value, ignoring case when comparing strings
10
- def self.array_contains(array, value, ignore_case)
11
- return false unless array.is_a?(Array) && !value.nil?
12
- if value.is_a?(String) && match_string_in_array(array, value, ignore_case, ->(a, b) { a == b })
13
- return true
14
- end
15
- return array.include?(value)
16
- end
17
-
18
9
  # returns true if array has any element that evaluates to true with value using func lambda, ignoring case
19
10
  def self.match_string_in_array(array, value, ignore_case, func)
20
- return false unless array.is_a?(Array) && value.is_a?(String)
21
- array.any?{ |s| s.is_a?(String) && ((ignore_case && func.call(value.downcase, s.downcase)) || func.call(value, s)) } rescue false
11
+ return false unless array.is_a?(Array) && !value.nil?
12
+ str_value = value.to_s
13
+ array.any?{ |s| !s.nil? && ((ignore_case && func.call(str_value.downcase, s.to_s.downcase)) || func.call(str_value, s.to_s)) } rescue false
22
14
  end
23
15
 
24
16
  def self.compare_times(a, b, func)
data/lib/evaluator.rb CHANGED
@@ -32,18 +32,21 @@ class Evaluator
32
32
 
33
33
  def eval_spec(user, config)
34
34
  if config['enabled']
35
+ exposures = []
35
36
  i = 0
36
37
  until i >= config['rules'].length do
37
38
  rule = config['rules'][i]
38
39
  result = self.eval_rule(user, rule)
39
40
  return $fetch_from_server if result == $fetch_from_server
40
- if result
41
+ exposures = exposures + result["exposures"] if result["exposures"].is_a? Array
42
+ if result['value']
41
43
  pass = self.eval_pass_percent(user, rule, config['salt'])
42
44
  return ConfigResult.new(
43
45
  config['name'],
44
46
  pass,
45
47
  pass ? rule['returnValue'] : config['defaultValue'],
46
48
  rule['id'],
49
+ exposures
47
50
  )
48
51
  end
49
52
 
@@ -51,17 +54,28 @@ class Evaluator
51
54
  end
52
55
  end
53
56
 
54
- ConfigResult.new(config['name'], false, config['defaultValue'], 'default')
57
+ ConfigResult.new(config['name'], false, config['defaultValue'], 'default', [])
55
58
  end
56
59
 
57
60
  def eval_rule(user, rule)
61
+ exposures = []
62
+ pass = true
58
63
  i = 0
59
64
  until i >= rule['conditions'].length do
60
65
  result = self.eval_condition(user, rule['conditions'][i])
61
- return result unless result == true
66
+ if result == $fetch_from_server
67
+ return $fetch_from_server
68
+ end
69
+
70
+ if result.is_a?(Hash)
71
+ exposures = exposures + result["exposures"] if result["exposures"].is_a? Array
72
+ pass = false if result["value"] == false
73
+ elsif result == false
74
+ pass = false
75
+ end
62
76
  i += 1
63
77
  end
64
- true
78
+ { "value" => pass, "exposures" => exposures }
65
79
  end
66
80
 
67
81
  def eval_condition(user, condition)
@@ -82,7 +96,18 @@ class Evaluator
82
96
  when 'fail_gate', 'pass_gate'
83
97
  other_gate_result = self.check_gate(user, target)
84
98
  return $fetch_from_server if other_gate_result == $fetch_from_server
85
- return type == 'pass_gate' ? other_gate_result.gate_value : !other_gate_result.gate_value
99
+
100
+ gate_value = other_gate_result&.gate_value == true
101
+ new_exposure = {
102
+ "gate" => target,
103
+ "gateValue" => gate_value ? "true" : "false",
104
+ "ruleID" => other_gate_result&.rule_id
105
+ }
106
+ exposures = other_gate_result&.secondary_exposures&.append(new_exposure)
107
+ return {
108
+ "value" => type == 'pass_gate' ? gate_value : !gate_value,
109
+ "exposures" => exposures
110
+ }
86
111
  when 'ip_based'
87
112
  value = get_value_from_user(user, field) || get_value_from_ip(user, field)
88
113
  return $fetch_from_server if value == $fetch_from_server
@@ -138,13 +163,13 @@ class Evaluator
138
163
 
139
164
  # array operations
140
165
  when 'any'
141
- return EvaluationHelpers::array_contains(target, value, true)
166
+ return EvaluationHelpers::match_string_in_array(target, value, true, ->(a, b) { a == b })
142
167
  when 'none'
143
- return !EvaluationHelpers::array_contains(target, value, true)
168
+ return !EvaluationHelpers::match_string_in_array(target, value, true, ->(a, b) { a == b })
144
169
  when 'any_case_sensitive'
145
- return EvaluationHelpers::array_contains(target, value, false)
170
+ return EvaluationHelpers::match_string_in_array(target, value, false, ->(a, b) { a == b })
146
171
  when 'none_case_sensitive'
147
- return !EvaluationHelpers::array_contains(target, value, false)
172
+ return !EvaluationHelpers::match_string_in_array(target, value, false, ->(a, b) { a == b })
148
173
 
149
174
  #string
150
175
  when 'str_starts_with_any'
@@ -153,6 +178,8 @@ class Evaluator
153
178
  return EvaluationHelpers::match_string_in_array(target, value, true, ->(a, b) { a.end_with?(b) })
154
179
  when 'str_contains_any'
155
180
  return EvaluationHelpers::match_string_in_array(target, value, true, ->(a, b) { a.include?(b) })
181
+ when 'str_contains_none'
182
+ return !EvaluationHelpers::match_string_in_array(target, value, true, ->(a, b) { a.include?(b) })
156
183
  when 'str_matches'
157
184
  return (value.is_a?(String) && !(value =~ Regexp.new(target)).nil? rescue false)
158
185
  when 'eq'
data/lib/network.rb CHANGED
@@ -86,7 +86,7 @@ class Network
86
86
  def post_logs(events, statsig_metadata)
87
87
  begin
88
88
  json_body = JSON.generate({'events' => events, 'statsigMetadata' => statsig_metadata})
89
- post_helper('log_event', body: json_body, retries: 5)
89
+ post_helper('log_event', json_body, retries: 5)
90
90
  rescue
91
91
  end
92
92
  end
@@ -56,9 +56,11 @@ class StatsigDriver
56
56
 
57
57
  if res == $fetch_from_server
58
58
  res = check_gate_fallback(user, gate_name)
59
+ # exposure logged by the server
60
+ else
61
+ @logger.log_gate_exposure(user, res.name, res.gate_value, res.rule_id, res.secondary_exposures)
59
62
  end
60
63
 
61
- @logger.log_gate_exposure(user, res.name, res.gate_value, res.rule_id)
62
64
  res.gate_value
63
65
  end
64
66
 
@@ -80,11 +82,19 @@ class StatsigDriver
80
82
 
81
83
  if res == $fetch_from_server
82
84
  res = get_config_fallback(user, dynamic_config_name)
85
+ # exposure logged by the server
86
+ else
87
+ @logger.log_config_exposure(user, res.name, res.rule_id, res.secondary_exposures)
83
88
  end
84
89
 
85
- result_config = DynamicConfig.new(res.name, res.json_value, res.rule_id)
86
- @logger.log_config_exposure(user, result_config.name, result_config.rule_id)
87
- result_config
90
+ DynamicConfig.new(res.name, res.json_value, res.rule_id)
91
+ end
92
+
93
+ def get_experiment(user, experiment_name)
94
+ if !experiment_name.is_a?(String) || experiment_name.empty?
95
+ raise "Invalid experiment_name provided"
96
+ end
97
+ get_config(user, experiment_name)
88
98
  end
89
99
 
90
100
  def log_event(user, event_name, value = nil, metadata = nil)
data/lib/statsig_event.rb CHANGED
@@ -2,6 +2,7 @@ class StatsigEvent
2
2
  attr_accessor :value
3
3
  attr_accessor :metadata
4
4
  attr_accessor :statsig_metadata
5
+ attr_accessor :secondary_exposures
5
6
  attr_reader :user
6
7
 
7
8
  def initialize(event_name)
@@ -23,6 +24,7 @@ class StatsigEvent
23
24
  'user' => @user,
24
25
  'time' => @time,
25
26
  'statsigMetadata' => @statsig_metadata,
27
+ 'secondaryExposures' => @secondary_exposures
26
28
  }
27
29
  end
28
30
  end
@@ -21,7 +21,7 @@ class StatsigLogger
21
21
  end
22
22
  end
23
23
 
24
- def log_gate_exposure(user, gate_name, value, rule_id)
24
+ def log_gate_exposure(user, gate_name, value, rule_id, secondary_exposures)
25
25
  event = StatsigEvent.new($gate_exposure_event)
26
26
  event.user = user
27
27
  event.metadata = {
@@ -30,10 +30,11 @@ class StatsigLogger
30
30
  'ruleID' => rule_id
31
31
  }
32
32
  event.statsig_metadata = @statsig_metadata
33
+ event.secondary_exposures = secondary_exposures.is_a?(Array) ? secondary_exposures : []
33
34
  log_event(event)
34
35
  end
35
36
 
36
- def log_config_exposure(user, config_name, rule_id)
37
+ def log_config_exposure(user, config_name, rule_id, secondary_exposures)
37
38
  event = StatsigEvent.new($config_exposure_event)
38
39
  event.user = user
39
40
  event.metadata = {
@@ -41,6 +42,7 @@ class StatsigLogger
41
42
  'ruleID' => rule_id
42
43
  }
43
44
  event.statsig_metadata = @statsig_metadata
45
+ event.secondary_exposures = secondary_exposures.is_a?(Array) ? secondary_exposures : []
44
46
  log_event(event)
45
47
  end
46
48
 
@@ -51,11 +53,9 @@ class StatsigLogger
51
53
  if @events.length == 0
52
54
  return
53
55
  end
54
- flush_events = @events.map { |e| e.serialize() }
56
+ flush_events = @events.map { |e| e.serialize }
55
57
  @events = []
56
58
 
57
- Thread.new do
58
- @network.post_logs(flush_events, @statsig_metadata)
59
- end
59
+ @network.post_logs(flush_events, @statsig_metadata)
60
60
  end
61
61
  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.5.1
4
+ version: 1.6.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Statsig, Inc
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-09-01 00:00:00.000000000 Z
11
+ date: 2021-09-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler