growthbook 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/growthbook/context.rb +36 -32
- data/lib/growthbook/inline_experiment_result.rb +6 -1
- data/spec/cases.json +50 -0
- data/spec/context_spec.rb +2 -0
- 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: 9f6c9a059b34fad58095b5e0ea6b3710d7b427617a4cd4f9c7ddc8fdebd2a1da
|
4
|
+
data.tar.gz: c93b9e289a8c950dc3cfa5974062634e7c11f6123e7fd5e94f6ff8c88d262566
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0f1c46dac7d0e9dccc8641f67d7dbbe8d2af91ec18a4cf0fdcb13701042e0fad55bbcf35b91c330506816fa59cbb0129a8ae5a7f3fb103b308735268f3638937
|
7
|
+
data.tar.gz: c38fe86529984df54ee57560b01591c99a20b616f02d8296e9e81a7c1fc56d6755096c675e96e94a52c57c5e9e77435b497aed2d4a3ebb4c0cdcbd78f7d8085b
|
data/lib/growthbook/context.rb
CHANGED
@@ -77,7 +77,7 @@ module Growthbook
|
|
77
77
|
# Rollout or forced value rule
|
78
78
|
if rule.is_force?
|
79
79
|
unless rule.coverage.nil?
|
80
|
-
hash_value = get_attribute(rule.hash_attribute || 'id')
|
80
|
+
hash_value = get_attribute(rule.hash_attribute || 'id').to_s
|
81
81
|
next if hash_value.length.zero?
|
82
82
|
|
83
83
|
n = Growthbook::Util.hash(hash_value + key)
|
@@ -89,7 +89,7 @@ module Growthbook
|
|
89
89
|
next unless rule.is_experiment?
|
90
90
|
|
91
91
|
exp = rule.to_experiment(key)
|
92
|
-
result =
|
92
|
+
result = _run(exp, key)
|
93
93
|
|
94
94
|
next unless result.in_experiment
|
95
95
|
|
@@ -101,36 +101,55 @@ module Growthbook
|
|
101
101
|
end
|
102
102
|
|
103
103
|
def run(exp)
|
104
|
+
_run(exp)
|
105
|
+
end
|
106
|
+
|
107
|
+
def on?(key)
|
108
|
+
eval_feature(key).on
|
109
|
+
end
|
110
|
+
|
111
|
+
def off?(key)
|
112
|
+
eval_feature(key).off
|
113
|
+
end
|
114
|
+
|
115
|
+
def feature_value(key, fallback = nil)
|
116
|
+
value = eval_feature(key).value
|
117
|
+
value.nil? ? fallback : value
|
118
|
+
end
|
119
|
+
|
120
|
+
private
|
121
|
+
|
122
|
+
def _run(exp, feature_id="")
|
104
123
|
key = exp.key
|
105
124
|
|
106
125
|
# 1. If experiment doesn't have enough variations, return immediately
|
107
|
-
return get_experiment_result(exp) if exp.variations.length < 2
|
126
|
+
return get_experiment_result(exp, -1, false, feature_id) if exp.variations.length < 2
|
108
127
|
|
109
128
|
# 2. If context is disabled, return immediately
|
110
|
-
return get_experiment_result(exp) unless @enabled
|
129
|
+
return get_experiment_result(exp, -1, false, feature_id) unless @enabled
|
111
130
|
|
112
131
|
# 3. If forced via URL querystring
|
113
132
|
if @url
|
114
133
|
qsOverride = Util.get_query_string_override(key, @url, exp.variations.length)
|
115
|
-
return get_experiment_result(exp, qsOverride) unless qsOverride.nil?
|
134
|
+
return get_experiment_result(exp, qsOverride, false, feature_id) unless qsOverride.nil?
|
116
135
|
end
|
117
136
|
|
118
137
|
# 4. If variation is forced in the context, return the forced value
|
119
|
-
return get_experiment_result(exp, @forced_variations[key.to_s]) if @forced_variations.key?(key.to_s)
|
138
|
+
return get_experiment_result(exp, @forced_variations[key.to_s], false, feature_id) if @forced_variations.key?(key.to_s)
|
120
139
|
|
121
140
|
# 5. Exclude if not active
|
122
|
-
return get_experiment_result(exp) unless exp.active
|
141
|
+
return get_experiment_result(exp, -1, false, feature_id) unless exp.active
|
123
142
|
|
124
143
|
# 6. Get hash_attribute/value and return if empty
|
125
144
|
hash_attribute = exp.hash_attribute || 'id'
|
126
|
-
hash_value = get_attribute(hash_attribute)
|
127
|
-
return get_experiment_result(exp) if hash_value.length.zero?
|
145
|
+
hash_value = get_attribute(hash_attribute).to_s
|
146
|
+
return get_experiment_result(exp, -1, false, feature_id) if hash_value.length.zero?
|
128
147
|
|
129
148
|
# 7. Exclude if user not in namespace
|
130
|
-
return get_experiment_result(exp) if exp.namespace && !Growthbook::Util.in_namespace(hash_value, exp.namespace)
|
149
|
+
return get_experiment_result(exp, -1, false, feature_id) if exp.namespace && !Growthbook::Util.in_namespace(hash_value, exp.namespace)
|
131
150
|
|
132
151
|
# 8. Exclude if condition is false
|
133
|
-
return get_experiment_result(exp) if exp.condition && !condition_passes(exp.condition)
|
152
|
+
return get_experiment_result(exp, -1, false, feature_id) if exp.condition && !condition_passes(exp.condition)
|
134
153
|
|
135
154
|
# 9. Calculate bucket ranges and choose one
|
136
155
|
ranges = Growthbook::Util.get_bucket_ranges(
|
@@ -142,16 +161,16 @@ module Growthbook
|
|
142
161
|
assigned = Growthbook::Util.choose_variation(n, ranges)
|
143
162
|
|
144
163
|
# 10. Return if not in experiment
|
145
|
-
return get_experiment_result(exp) if assigned.negative?
|
164
|
+
return get_experiment_result(exp, -1, false, feature_id) if assigned.negative?
|
146
165
|
|
147
166
|
# 11. Experiment has a forced variation
|
148
|
-
return get_experiment_result(exp, exp.force) unless exp.force.nil?
|
167
|
+
return get_experiment_result(exp, exp.force, false, feature_id) unless exp.force.nil?
|
149
168
|
|
150
169
|
# 12. Exclude if in QA mode
|
151
|
-
return get_experiment_result(exp) if @qa_mode
|
170
|
+
return get_experiment_result(exp, -1, false, feature_id) if @qa_mode
|
152
171
|
|
153
172
|
# 13. Build the result object
|
154
|
-
result = get_experiment_result(exp, assigned, true)
|
173
|
+
result = get_experiment_result(exp, assigned, true, feature_id)
|
155
174
|
|
156
175
|
# 14. Fire tracking callback
|
157
176
|
track_experiment(exp, result)
|
@@ -160,21 +179,6 @@ module Growthbook
|
|
160
179
|
result
|
161
180
|
end
|
162
181
|
|
163
|
-
def on?(key)
|
164
|
-
eval_feature(key).on
|
165
|
-
end
|
166
|
-
|
167
|
-
def off?(key)
|
168
|
-
eval_feature(key).off
|
169
|
-
end
|
170
|
-
|
171
|
-
def feature_value(key, fallback = nil)
|
172
|
-
value = eval_feature(key).value
|
173
|
-
value.nil? ? fallback : value
|
174
|
-
end
|
175
|
-
|
176
|
-
private
|
177
|
-
|
178
182
|
def stringify_keys(hash)
|
179
183
|
new_hash = {}
|
180
184
|
hash.each do |key, value|
|
@@ -187,7 +191,7 @@ module Growthbook
|
|
187
191
|
Growthbook::Conditions.eval_condition(@attributes, condition)
|
188
192
|
end
|
189
193
|
|
190
|
-
def get_experiment_result(experiment, variation_index = -1, hash_used = false)
|
194
|
+
def get_experiment_result(experiment, variation_index = -1, hash_used = false, feature_id = "")
|
191
195
|
in_experiment = true
|
192
196
|
if variation_index.negative? || variation_index >= experiment.variations.length
|
193
197
|
variation_index = 0
|
@@ -198,7 +202,7 @@ module Growthbook
|
|
198
202
|
hash_value = get_attribute(hash_attribute)
|
199
203
|
|
200
204
|
Growthbook::InlineExperimentResult.new(hash_used, in_experiment, variation_index,
|
201
|
-
experiment.variations[variation_index], hash_attribute, hash_value)
|
205
|
+
experiment.variations[variation_index], hash_attribute, hash_value, feature_id)
|
202
206
|
end
|
203
207
|
|
204
208
|
def get_feature_result(value, source, experiment = nil, experiment_result = nil)
|
@@ -26,13 +26,16 @@ module Growthbook
|
|
26
26
|
# @return [String]
|
27
27
|
attr_reader :hash_value
|
28
28
|
|
29
|
+
attr_reader :feature_id
|
30
|
+
|
29
31
|
def initialize(
|
30
32
|
hash_used,
|
31
33
|
in_experiment,
|
32
34
|
variation_id,
|
33
35
|
value,
|
34
36
|
hash_attribute,
|
35
|
-
hash_value
|
37
|
+
hash_value,
|
38
|
+
feature_id
|
36
39
|
)
|
37
40
|
|
38
41
|
@hash_used = hash_used
|
@@ -41,6 +44,7 @@ module Growthbook
|
|
41
44
|
@value = value
|
42
45
|
@hash_attribute = hash_attribute
|
43
46
|
@hash_value = hash_value
|
47
|
+
@feature_id = feature_id
|
44
48
|
end
|
45
49
|
|
46
50
|
def to_json(*_args)
|
@@ -51,6 +55,7 @@ module Growthbook
|
|
51
55
|
res['value'] = @value
|
52
56
|
res['hashAttribute'] = @hash_attribute
|
53
57
|
res['hashValue'] = @hash_value
|
58
|
+
res['featureId'] = @feature_id.to_s
|
54
59
|
res
|
55
60
|
end
|
56
61
|
end
|
data/spec/cases.json
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
{
|
2
|
+
"specVersion": "0.2.2",
|
2
3
|
"evalCondition": [
|
3
4
|
[
|
4
5
|
"$not - pass",
|
@@ -1704,6 +1705,7 @@
|
|
1704
1705
|
"variations": ["a", "b", "c"]
|
1705
1706
|
},
|
1706
1707
|
"experimentResult": {
|
1708
|
+
"featureId": "feature",
|
1707
1709
|
"value": "c",
|
1708
1710
|
"variationId": 2,
|
1709
1711
|
"inExperiment": true,
|
@@ -1740,6 +1742,7 @@
|
|
1740
1742
|
"variations": ["a", "b", "c"]
|
1741
1743
|
},
|
1742
1744
|
"experimentResult": {
|
1745
|
+
"featureId": "feature",
|
1743
1746
|
"value": "a",
|
1744
1747
|
"variationId": 0,
|
1745
1748
|
"inExperiment": true,
|
@@ -1776,6 +1779,7 @@
|
|
1776
1779
|
"variations": ["a", "b", "c"]
|
1777
1780
|
},
|
1778
1781
|
"experimentResult": {
|
1782
|
+
"featureId": "feature",
|
1779
1783
|
"value": "b",
|
1780
1784
|
"variationId": 1,
|
1781
1785
|
"inExperiment": true,
|
@@ -1824,6 +1828,7 @@
|
|
1824
1828
|
"weights": [0.1, 0.9]
|
1825
1829
|
},
|
1826
1830
|
"experimentResult": {
|
1831
|
+
"featureId": "feature",
|
1827
1832
|
"value": false,
|
1828
1833
|
"variationId": 1,
|
1829
1834
|
"inExperiment": true,
|
@@ -1989,6 +1994,42 @@
|
|
1989
1994
|
"source": "force"
|
1990
1995
|
}
|
1991
1996
|
],
|
1997
|
+
[
|
1998
|
+
"handles integer hashAttribute",
|
1999
|
+
{
|
2000
|
+
"attributes": { "id": 123 },
|
2001
|
+
"features": {
|
2002
|
+
"feature": {
|
2003
|
+
"defaultValue": 0,
|
2004
|
+
"rules": [
|
2005
|
+
{
|
2006
|
+
"variations": [0, 1]
|
2007
|
+
}
|
2008
|
+
]
|
2009
|
+
}
|
2010
|
+
}
|
2011
|
+
},
|
2012
|
+
"feature",
|
2013
|
+
{
|
2014
|
+
"value": 1,
|
2015
|
+
"on": true,
|
2016
|
+
"off": false,
|
2017
|
+
"source": "experiment",
|
2018
|
+
"experiment": {
|
2019
|
+
"key": "feature",
|
2020
|
+
"variations": [0, 1]
|
2021
|
+
},
|
2022
|
+
"experimentResult": {
|
2023
|
+
"featureId": "feature",
|
2024
|
+
"hashAttribute": "id",
|
2025
|
+
"hashValue": 123,
|
2026
|
+
"hashUsed": true,
|
2027
|
+
"inExperiment": true,
|
2028
|
+
"value": 1,
|
2029
|
+
"variationId": 1
|
2030
|
+
}
|
2031
|
+
}
|
2032
|
+
],
|
1992
2033
|
[
|
1993
2034
|
"skip experiment on missing hashAttribute",
|
1994
2035
|
{
|
@@ -2048,6 +2089,7 @@
|
|
2048
2089
|
"variations": [0, 1, 2, 3]
|
2049
2090
|
},
|
2050
2091
|
"experimentResult": {
|
2092
|
+
"featureId": "feature",
|
2051
2093
|
"value": 1,
|
2052
2094
|
"variationId": 1,
|
2053
2095
|
"inExperiment": true,
|
@@ -2371,6 +2413,14 @@
|
|
2371
2413
|
false,
|
2372
2414
|
false
|
2373
2415
|
],
|
2416
|
+
[
|
2417
|
+
"null id",
|
2418
|
+
{ "attributes": { "id": null } },
|
2419
|
+
{ "key": "my-test", "variations": [0, 1] },
|
2420
|
+
0,
|
2421
|
+
false,
|
2422
|
+
false
|
2423
|
+
],
|
2374
2424
|
[
|
2375
2425
|
"missing id",
|
2376
2426
|
{ "attributes": {} },
|
data/spec/context_spec.rb
CHANGED
@@ -95,6 +95,7 @@ describe 'context' do
|
|
95
95
|
gb.on? :feature1
|
96
96
|
|
97
97
|
expect(gb.impressions["feature1"].to_json).to eq({
|
98
|
+
"featureId" => "feature1",
|
98
99
|
"hashAttribute" => "id",
|
99
100
|
"hashValue" => "123",
|
100
101
|
"inExperiment" => true,
|
@@ -109,6 +110,7 @@ describe 'context' do
|
|
109
110
|
"variations" => [2, 3]
|
110
111
|
},
|
111
112
|
{
|
113
|
+
"featureId" => "feature1",
|
112
114
|
"hashAttribute" => "id",
|
113
115
|
"hashValue" => "123",
|
114
116
|
"inExperiment" => true,
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: growthbook
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- GrowthBook
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-09-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rspec
|