statsig 1.4.0 → 1.6.2

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: 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