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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 28649218e25654a513fd965aeed205a4208341c265ea9e06d1b2f6efe9a4b1eb
4
- data.tar.gz: 165ef4c260ec96a6c77b47acdfdd218039a7067156b0669d7b44adbf4c30382b
3
+ metadata.gz: 9f6c9a059b34fad58095b5e0ea6b3710d7b427617a4cd4f9c7ddc8fdebd2a1da
4
+ data.tar.gz: c93b9e289a8c950dc3cfa5974062634e7c11f6123e7fd5e94f6ff8c88d262566
5
5
  SHA512:
6
- metadata.gz: aa1bd7678e81a58d64a51a4f9d035c76cb9ee75ae91584b17c6ff92338e543be58935da3542f77c6e3c6d1cc5fcf030eb804a09d547fd8533511474d6c638f4a
7
- data.tar.gz: 9600cc883324faa2c7dcbbf4aa26e8469f0952057f3cf4b449c38cb59c0e6cd37eea246943f99750f3d54a1259fdebb622a14ec4b951a957cecf8bd924d107c0
6
+ metadata.gz: 0f1c46dac7d0e9dccc8641f67d7dbbe8d2af91ec18a4cf0fdcb13701042e0fad55bbcf35b91c330506816fa59cbb0129a8ae5a7f3fb103b308735268f3638937
7
+ data.tar.gz: c38fe86529984df54ee57560b01591c99a20b616f02d8296e9e81a7c1fc56d6755096c675e96e94a52c57c5e9e77435b497aed2d4a3ebb4c0cdcbd78f7d8085b
@@ -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 = run(exp)
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.2.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-05-24 00:00:00.000000000 Z
11
+ date: 2022-09-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec