statsig 1.5.1 → 1.6.3
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 +4 -4
- data/lib/config_result.rb +3 -1
- data/lib/evaluation_helpers.rb +3 -11
- data/lib/evaluator.rb +36 -9
- data/lib/network.rb +1 -1
- data/lib/statsig_driver.rb +14 -4
- data/lib/statsig_event.rb +2 -0
- data/lib/statsig_logger.rb +6 -6
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9192f1c9886d3a3b18c0a2e70599e998b4784b23245e9064018ec31c98881b25
|
4
|
+
data.tar.gz: eb329d5f742ce2f77391f3f33ba4229852c2c68827643159b51fd870c1fb21d9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
data/lib/evaluation_helpers.rb
CHANGED
@@ -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.
|
21
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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::
|
166
|
+
return EvaluationHelpers::match_string_in_array(target, value, true, ->(a, b) { a == b })
|
142
167
|
when 'none'
|
143
|
-
return !EvaluationHelpers::
|
168
|
+
return !EvaluationHelpers::match_string_in_array(target, value, true, ->(a, b) { a == b })
|
144
169
|
when 'any_case_sensitive'
|
145
|
-
return EvaluationHelpers::
|
170
|
+
return EvaluationHelpers::match_string_in_array(target, value, false, ->(a, b) { a == b })
|
146
171
|
when 'none_case_sensitive'
|
147
|
-
return !EvaluationHelpers::
|
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',
|
89
|
+
post_helper('log_event', json_body, retries: 5)
|
90
90
|
rescue
|
91
91
|
end
|
92
92
|
end
|
data/lib/statsig_driver.rb
CHANGED
@@ -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
|
-
|
86
|
-
|
87
|
-
|
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
|
data/lib/statsig_logger.rb
CHANGED
@@ -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
|
-
|
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.
|
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-
|
11
|
+
date: 2021-09-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|