growthbook 0.2.0 → 0.3.0
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/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
|