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