statsig 1.4.0 → 1.6.2

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: 3ac164a57fadf37c1c9ce2b978df30da43901cc3b53e03e1b3e52a72e19981cb
4
- data.tar.gz: aad0387cd6a80bfd3cbf501118466f444790ec387b45a96d562a3256331ae034
3
+ metadata.gz: 325d5430e48a3d71b68d2e284e20b66b0052bf1a4d9e99e7a92a84069b680ab8
4
+ data.tar.gz: b227e1fede44928035b1a03ebc8e8c006425466b50b007f89c452f6a47b51cd6
5
5
  SHA512:
6
- metadata.gz: f7eeefd82a45217becadee9783d2616de5e6997d29236fecea526947a34e54f921125fbf018f57a1fbf2f7560c00d80721f632a9498e0f065b6914b035beef5f
7
- data.tar.gz: 24b8c55535b7656109b669d898351e536953c83ecc6858e94eabacbdb36f8c9e600b55bb9cd06fc8b043a84305f351e645c07a673a85c1688668de6181c125f3
6
+ metadata.gz: 28017886b1a5d3f5c37e36680f0dfbd300cf5a0f6e0678430af69631486bc88689a622d74a63da1687ef7d610d612227d68cb58b85a7572b7cc5cfcaee0794dd
7
+ data.tar.gz: 10a66e7674df116a65351b97b8d27055bacd46f021309c3256bc5605fe3a972416e2c595135262fc9d18117e447a5038db3ebe1b89cb71495f308430fe4d2a32
@@ -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
@@ -84,10 +84,10 @@ class Evaluator
84
84
  return $fetch_from_server if other_gate_result == $fetch_from_server
85
85
  return type == 'pass_gate' ? other_gate_result.gate_value : !other_gate_result.gate_value
86
86
  when 'ip_based'
87
- value = get_value_from_user(user, field) || get_value_from_ip(user&.value_lookup['ip'], field)
87
+ value = get_value_from_user(user, field) || get_value_from_ip(user, field)
88
88
  return $fetch_from_server if value == $fetch_from_server
89
89
  when 'ua_based'
90
- value = get_value_from_user(user, field) || get_value_from_ua(user&.value_lookup['userAgent'], field)
90
+ value = get_value_from_user(user, field) || get_value_from_ua(user, field)
91
91
  return $fetch_from_server if value == $fetch_from_server
92
92
  when 'user_field'
93
93
  value = get_value_from_user(user, field)
@@ -138,13 +138,13 @@ class Evaluator
138
138
 
139
139
  # array operations
140
140
  when 'any'
141
- return EvaluationHelpers::array_contains(target, value, true)
141
+ return EvaluationHelpers::match_string_in_array(target, value, true, ->(a, b) { a == b })
142
142
  when 'none'
143
- return !EvaluationHelpers::array_contains(target, value, true)
143
+ return !EvaluationHelpers::match_string_in_array(target, value, true, ->(a, b) { a == b })
144
144
  when 'any_case_sensitive'
145
- return EvaluationHelpers::array_contains(target, value, false)
145
+ return EvaluationHelpers::match_string_in_array(target, value, false, ->(a, b) { a == b })
146
146
  when 'none_case_sensitive'
147
- return !EvaluationHelpers::array_contains(target, value, false)
147
+ return !EvaluationHelpers::match_string_in_array(target, value, false, ->(a, b) { a == b })
148
148
 
149
149
  #string
150
150
  when 'str_starts_with_any'
@@ -153,6 +153,8 @@ class Evaluator
153
153
  return EvaluationHelpers::match_string_in_array(target, value, true, ->(a, b) { a.end_with?(b) })
154
154
  when 'str_contains_any'
155
155
  return EvaluationHelpers::match_string_in_array(target, value, true, ->(a, b) { a.include?(b) })
156
+ when 'str_contains_none'
157
+ return !EvaluationHelpers::match_string_in_array(target, value, true, ->(a, b) { a.include?(b) })
156
158
  when 'str_matches'
157
159
  return (value.is_a?(String) && !(value =~ Regexp.new(target)).nil? rescue false)
158
160
  when 'eq'
@@ -177,13 +179,22 @@ class Evaluator
177
179
 
178
180
  user_lookup_table = user&.value_lookup
179
181
  return nil unless user_lookup_table.is_a?(Hash)
180
- return user_lookup_table[field.downcase] if user_lookup_table.has_key?(field.downcase)
182
+ return user_lookup_table[field.downcase] if user_lookup_table.has_key?(field.downcase) && !user_lookup_table[field.downcase].nil?
181
183
 
182
184
  user_custom = user_lookup_table['custom']
183
- return nil unless user_custom.is_a?(Hash)
184
- user_custom.each do |key, value|
185
- return value if key.downcase.casecmp?(field.downcase)
185
+ if user_custom.is_a?(Hash)
186
+ user_custom.each do |key, value|
187
+ return value if key.downcase.casecmp?(field.downcase) && !value.nil?
188
+ end
189
+ end
190
+
191
+ private_attributes = user_lookup_table['privateAttributes']
192
+ if private_attributes.is_a?(Hash)
193
+ private_attributes.each do |key, value|
194
+ return value if key.downcase.casecmp?(field.downcase) && !value.nil?
195
+ end
186
196
  end
197
+
187
198
  nil
188
199
  end
189
200
 
@@ -197,17 +208,19 @@ class Evaluator
197
208
  nil
198
209
  end
199
210
 
200
- def get_value_from_ip(ip, field)
201
- return nil unless ip.is_a?(String) && field.is_a?(String)
211
+ def get_value_from_ip(user, field)
212
+ return nil unless user.is_a?(StatsigUser) && field.is_a?(String) && field.downcase == 'country'
213
+ ip = get_value_from_user(user, 'ip')
214
+ return nil unless ip.is_a?(String)
202
215
 
203
- if field.downcase != 'country'
204
- return $fetch_from_server
205
- end
206
216
  CountryLookup.lookup_ip_string(ip)
207
217
  end
208
218
 
209
- def get_value_from_ua(ua, field)
210
- return nil unless ua.is_a?(String) && field.is_a?(String)
219
+ def get_value_from_ua(user, field)
220
+ return nil unless user.is_a?(StatsigUser) && field.is_a?(String)
221
+ ua = get_value_from_user(user, 'userAgent')
222
+ return nil unless ua.is_a?(String)
223
+
211
224
  parsed = @ua_parser.parse ua
212
225
  os = parsed.os
213
226
  case field.downcase
data/lib/network.rb CHANGED
@@ -39,7 +39,7 @@ class Network
39
39
 
40
40
  def check_gate(user, gate_name)
41
41
  begin
42
- request_body = JSON.generate({'user' => user&.serialize, 'gateName' => gate_name})
42
+ request_body = JSON.generate({'user' => user&.serialize(false), 'gateName' => gate_name})
43
43
  response = post_helper('check_gate', request_body)
44
44
  return JSON.parse(response.body) unless response.nil?
45
45
  false
@@ -50,7 +50,7 @@ class Network
50
50
 
51
51
  def get_config(user, dynamic_config_name)
52
52
  begin
53
- request_body = JSON.generate({'user' => user&.serialize, 'configName' => dynamic_config_name})
53
+ request_body = JSON.generate({'user' => user&.serialize(false), 'configName' => dynamic_config_name})
54
54
  response = post_helper('get_config', request_body)
55
55
  return JSON.parse(response.body) unless response.nil?
56
56
  nil
@@ -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)
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,12 @@ 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)
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)
88
91
  end
89
92
 
90
93
  def log_event(user, event_name, value = nil, metadata = nil)
@@ -96,7 +99,7 @@ class StatsigDriver
96
99
  user = normalize_user(user)
97
100
 
98
101
  event = StatsigEvent.new(event_name)
99
- event.user = user&.serialize
102
+ event.user = user
100
103
  event.value = value
101
104
  event.metadata = metadata
102
105
  event.statsig_metadata = @statsig_metadata
data/lib/statsig_event.rb CHANGED
@@ -1,15 +1,22 @@
1
1
  class StatsigEvent
2
2
  attr_accessor :value
3
- attr_accessor :user
4
3
  attr_accessor :metadata
5
4
  attr_accessor :statsig_metadata
5
+ attr_reader :user
6
+
6
7
  def initialize(event_name)
7
8
  @event_name = event_name
8
9
  @time = Time.now.to_f * 1000
9
10
  end
10
11
 
12
+ def user=(value)
13
+ if value.is_a?(StatsigUser)
14
+ @user = value.serialize(true)
15
+ end
16
+ end
17
+
11
18
  def serialize
12
- return {
19
+ {
13
20
  'eventName' => @event_name,
14
21
  'metadata' => @metadata,
15
22
  'value' => @value,
@@ -54,8 +54,6 @@ class StatsigLogger
54
54
  flush_events = @events.map { |e| e.serialize() }
55
55
  @events = []
56
56
 
57
- Thread.new do
58
- @network.post_logs(flush_events, @statsig_metadata)
59
- end
57
+ @network.post_logs(flush_events, @statsig_metadata)
60
58
  end
61
59
  end
data/lib/statsig_user.rb CHANGED
@@ -7,6 +7,7 @@ class StatsigUser
7
7
  attr_accessor :locale
8
8
  attr_accessor :app_version
9
9
  attr_accessor :statsig_environment
10
+ attr_accessor :private_attributes
10
11
 
11
12
  def custom
12
13
  @custom
@@ -29,11 +30,12 @@ class StatsigUser
29
30
  @app_version = user_hash['appVersion'] || user_hash['app_version']
30
31
  @custom = user_hash['custom']
31
32
  @statsig_environment = user_hash['statsigEnvironment']
33
+ @private_attributes = user_hash['privateAttributes']
32
34
  end
33
35
  end
34
36
 
35
- def serialize
36
- {
37
+ def serialize(for_logging)
38
+ hash = {
37
39
  'userID' => @user_id,
38
40
  'email' => @email,
39
41
  'ip' => @ip,
@@ -43,7 +45,12 @@ class StatsigUser
43
45
  'appVersion' => @app_version,
44
46
  'custom' => @custom,
45
47
  'statsigEnvironment' => @statsig_environment,
48
+ 'privateAttributes' => @private_attributes,
46
49
  }
50
+ if for_logging
51
+ hash.delete('privateAttributes')
52
+ end
53
+ hash
47
54
  end
48
55
 
49
56
  def value_lookup
@@ -62,6 +69,7 @@ class StatsigUser
62
69
  'appversion' => @app_version,
63
70
  'app_version' => @app_version,
64
71
  'custom' => @custom,
72
+ 'privateAttributes' => @private_attributes,
65
73
  }
66
74
  end
67
75
  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.4.0
4
+ version: 1.6.2
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-08-13 00:00:00.000000000 Z
11
+ date: 2021-09-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler