growthbook 0.3.0 → 1.0.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/conditions.rb +13 -13
- data/lib/growthbook/context.rb +115 -39
- data/lib/growthbook/feature.rb +4 -3
- data/lib/growthbook/feature_result.rb +3 -2
- data/lib/growthbook/feature_rule.rb +92 -32
- data/lib/growthbook/inline_experiment.rb +71 -48
- data/lib/growthbook/inline_experiment_result.rb +57 -38
- data/lib/growthbook/util.rb +29 -42
- data/lib/growthbook.rb +1 -5
- metadata +34 -22
- data/lib/growthbook/client.rb +0 -67
- data/lib/growthbook/experiment.rb +0 -72
- data/lib/growthbook/experiment_result.rb +0 -43
- data/lib/growthbook/lookup_result.rb +0 -44
- data/lib/growthbook/user.rb +0 -165
- data/spec/cases.json +0 -2923
- data/spec/client_spec.rb +0 -57
- data/spec/context_spec.rb +0 -124
- data/spec/json_spec.rb +0 -160
- data/spec/user_spec.rb +0 -213
- data/spec/util_spec.rb +0 -154
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 95b90dcba429217cc585f4ea2007e3d7aab0f3489dc93ee41d7fa72b847b7f53
|
4
|
+
data.tar.gz: 363c48f172798a02d899cfdc6ffb692ab1f85b45cf205e0d4c5dc6339c86c83a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6b8c950602e92cb66a644e6780827410d3c2e5eb71078adb95b469fbbe1c9b7f9925a6d3cf5917c345b9a5d04feabf21198d0349a57d5c597e7dca92f004bf43
|
7
|
+
data.tar.gz: ded0ea4cc0913bf8d93f8f61a34b622579d7346dfe02f121d011e82b11920e7703c192e8afb6331c2ac0d5245a435473e54707907e6116e46d4b4b01a5a3c394
|
@@ -3,6 +3,8 @@
|
|
3
3
|
require 'json'
|
4
4
|
|
5
5
|
module Growthbook
|
6
|
+
# internal use only
|
7
|
+
# Utils for condition evaluation
|
6
8
|
class Conditions
|
7
9
|
# Evaluate a targeting conditions hash against an attributes hash
|
8
10
|
# Both attributes and conditions only have string keys (no symbols)
|
@@ -25,7 +27,7 @@ module Growthbook
|
|
25
27
|
when Array
|
26
28
|
return condition.map { |v| parse_condition(v) }
|
27
29
|
when Hash
|
28
|
-
return condition.
|
30
|
+
return condition.to_h { |k, v| [k.to_s, parse_condition(v)] }
|
29
31
|
end
|
30
32
|
|
31
33
|
condition
|
@@ -47,7 +49,7 @@ module Growthbook
|
|
47
49
|
true
|
48
50
|
end
|
49
51
|
|
50
|
-
def self.
|
52
|
+
def self.operator_object?(obj)
|
51
53
|
obj.each do |key, _value|
|
52
54
|
return false if key[0] != '$'
|
53
55
|
end
|
@@ -58,7 +60,7 @@ module Growthbook
|
|
58
60
|
return 'string' if attribute_value.is_a? String
|
59
61
|
return 'number' if attribute_value.is_a? Integer
|
60
62
|
return 'number' if attribute_value.is_a? Float
|
61
|
-
return 'boolean' if
|
63
|
+
return 'boolean' if [true, false].include?(attribute_value)
|
62
64
|
return 'array' if attribute_value.is_a? Array
|
63
65
|
return 'null' if attribute_value.nil?
|
64
66
|
|
@@ -70,18 +72,16 @@ module Growthbook
|
|
70
72
|
current = attributes
|
71
73
|
|
72
74
|
parts.each do |value|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
return nil
|
77
|
-
end
|
75
|
+
return nil unless current.is_a?(Hash) && current&.key?(value)
|
76
|
+
|
77
|
+
current = current[value]
|
78
78
|
end
|
79
79
|
|
80
80
|
current
|
81
81
|
end
|
82
82
|
|
83
83
|
def self.eval_condition_value(condition_value, attribute_value)
|
84
|
-
if condition_value.is_a?(Hash) &&
|
84
|
+
if condition_value.is_a?(Hash) && operator_object?(condition_value)
|
85
85
|
condition_value.each do |key, value|
|
86
86
|
return false unless eval_operator_condition(key, attribute_value, value)
|
87
87
|
end
|
@@ -94,7 +94,7 @@ module Growthbook
|
|
94
94
|
return false unless attribute_value.is_a? Array
|
95
95
|
|
96
96
|
attribute_value.each do |item|
|
97
|
-
if
|
97
|
+
if operator_object?(condition)
|
98
98
|
return true if eval_condition_value(condition, item)
|
99
99
|
elsif eval_condition(item, condition)
|
100
100
|
return true
|
@@ -147,10 +147,10 @@ module Growthbook
|
|
147
147
|
true
|
148
148
|
when '$exists'
|
149
149
|
exists = !attribute_value.nil?
|
150
|
-
if
|
151
|
-
!exists
|
152
|
-
else
|
150
|
+
if condition_value
|
153
151
|
exists
|
152
|
+
else
|
153
|
+
!exists
|
154
154
|
end
|
155
155
|
when '$type'
|
156
156
|
condition_value == get_type(attribute_value)
|
data/lib/growthbook/context.rb
CHANGED
@@ -1,9 +1,30 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Growthbook
|
4
|
+
# Context object passed into the GrowthBook constructor.
|
4
5
|
class Context
|
5
|
-
|
6
|
-
|
6
|
+
# @return [true, false] Switch to globally disable all experiments. Default true.
|
7
|
+
attr_accessor :enabled
|
8
|
+
|
9
|
+
# @return [String] The URL of the current page
|
10
|
+
attr_accessor :url
|
11
|
+
|
12
|
+
# @return [true, false, nil] If true, random assignment is disabled and only explicitly forced variations are used.
|
13
|
+
attr_accessor :qa_mode
|
14
|
+
|
15
|
+
# @return [Listener] An object that responds to some tracking methods that take experiment and result as arguments.
|
16
|
+
attr_accessor :listener
|
17
|
+
|
18
|
+
# @return [Hash] Map of user attributes that are used to assign variations
|
19
|
+
attr_reader :attributes
|
20
|
+
|
21
|
+
# @return [Hash] Feature definitions (usually pulled from an API or cache)
|
22
|
+
attr_reader :features
|
23
|
+
|
24
|
+
# @return [Hash] Force specific experiments to always assign a specific variation (used for QA)
|
25
|
+
attr_reader :forced_variations
|
26
|
+
|
27
|
+
attr_reader :impressions, :forced_features
|
7
28
|
|
8
29
|
def initialize(options = {})
|
9
30
|
@features = {}
|
@@ -62,9 +83,7 @@ module Growthbook
|
|
62
83
|
|
63
84
|
def eval_feature(key)
|
64
85
|
# Forced in the context
|
65
|
-
if @forced_features.key?(key.to_s)
|
66
|
-
return get_feature_result(@forced_features[key.to_s], 'override')
|
67
|
-
end
|
86
|
+
return get_feature_result(@forced_features[key.to_s], 'override') if @forced_features.key?(key.to_s)
|
68
87
|
|
69
88
|
# Return if we can't find the feature definition
|
70
89
|
feature = get_feature(key)
|
@@ -74,24 +93,28 @@ module Growthbook
|
|
74
93
|
# Targeting condition
|
75
94
|
next if rule.condition && !condition_passes(rule.condition)
|
76
95
|
|
77
|
-
#
|
78
|
-
if rule.
|
79
|
-
|
80
|
-
|
81
|
-
|
96
|
+
# If there are filters for who is included (e.g. namespaces)
|
97
|
+
next if rule.filters && filtered_out?(rule.filters)
|
98
|
+
|
99
|
+
# If this is a percentage rollout, skip if not included
|
100
|
+
if rule.force?
|
101
|
+
seed = rule.seed || key
|
102
|
+
hash_attribute = rule.hash_attribute || 'id'
|
103
|
+
included_in_rollout = included_in_rollout?(
|
104
|
+
seed: seed, hash_attribute: hash_attribute, range: rule.range,
|
105
|
+
coverage: rule.coverage, hash_version: rule.hash_version
|
106
|
+
)
|
107
|
+
next unless included_in_rollout
|
82
108
|
|
83
|
-
n = Growthbook::Util.hash(hash_value + key)
|
84
|
-
next if n > rule.coverage
|
85
|
-
end
|
86
109
|
return get_feature_result(rule.force, 'force')
|
87
110
|
end
|
88
111
|
# Experiment rule
|
89
|
-
next unless rule.
|
112
|
+
next unless rule.experiment?
|
90
113
|
|
91
114
|
exp = rule.to_experiment(key)
|
92
115
|
result = _run(exp, key)
|
93
116
|
|
94
|
-
next unless result.in_experiment
|
117
|
+
next unless result.in_experiment && !result.passthrough
|
95
118
|
|
96
119
|
return get_feature_result(result.value, 'experiment', exp, result)
|
97
120
|
end
|
@@ -119,58 +142,76 @@ module Growthbook
|
|
119
142
|
|
120
143
|
private
|
121
144
|
|
122
|
-
def _run(exp, feature_id=
|
145
|
+
def _run(exp, feature_id = '')
|
123
146
|
key = exp.key
|
124
147
|
|
125
148
|
# 1. If experiment doesn't have enough variations, return immediately
|
126
|
-
return get_experiment_result(exp, -1, false, feature_id) if exp.variations.length < 2
|
149
|
+
return get_experiment_result(exp, -1, hash_used: false, feature_id: feature_id) if exp.variations.length < 2
|
127
150
|
|
128
151
|
# 2. If context is disabled, return immediately
|
129
|
-
return get_experiment_result(exp, -1, false, feature_id) unless @enabled
|
152
|
+
return get_experiment_result(exp, -1, hash_used: false, feature_id: feature_id) unless @enabled
|
130
153
|
|
131
154
|
# 3. If forced via URL querystring
|
132
155
|
if @url
|
133
|
-
|
134
|
-
return get_experiment_result(exp,
|
156
|
+
qs_override = Util.get_query_string_override(key, @url, exp.variations.length)
|
157
|
+
return get_experiment_result(exp, qs_override, hash_used: false, feature_id: feature_id) unless qs_override.nil?
|
135
158
|
end
|
136
159
|
|
137
160
|
# 4. If variation is forced in the context, return the forced value
|
138
|
-
|
161
|
+
if @forced_variations.key?(key.to_s)
|
162
|
+
return get_experiment_result(
|
163
|
+
exp,
|
164
|
+
@forced_variations[key.to_s],
|
165
|
+
hash_used: false,
|
166
|
+
feature_id: feature_id
|
167
|
+
)
|
168
|
+
end
|
139
169
|
|
140
170
|
# 5. Exclude if not active
|
141
|
-
return get_experiment_result(exp, -1, false, feature_id) unless exp.active
|
171
|
+
return get_experiment_result(exp, -1, hash_used: false, feature_id: feature_id) unless exp.active
|
142
172
|
|
143
173
|
# 6. Get hash_attribute/value and return if empty
|
144
174
|
hash_attribute = exp.hash_attribute || 'id'
|
145
175
|
hash_value = get_attribute(hash_attribute).to_s
|
146
|
-
return get_experiment_result(exp, -1, false, feature_id) if hash_value.
|
176
|
+
return get_experiment_result(exp, -1, hash_used: false, feature_id: feature_id) if hash_value.empty?
|
147
177
|
|
148
|
-
# 7. Exclude if user
|
149
|
-
|
178
|
+
# 7. Exclude if user is filtered out (used to be called "namespace")
|
179
|
+
if exp.filters
|
180
|
+
return get_experiment_result(exp, -1, hash_used: false, feature_id: feature_id) if filtered_out?(exp.filters)
|
181
|
+
elsif exp.namespace && !Growthbook::Util.in_namespace(hash_value, exp.namespace)
|
182
|
+
return get_experiment_result(exp, -1, hash_used: false, feature_id: feature_id)
|
183
|
+
end
|
150
184
|
|
151
185
|
# 8. Exclude if condition is false
|
152
|
-
|
186
|
+
if exp.condition && !condition_passes(exp.condition)
|
187
|
+
return get_experiment_result(
|
188
|
+
exp,
|
189
|
+
-1,
|
190
|
+
hash_used: false,
|
191
|
+
feature_id: feature_id
|
192
|
+
)
|
193
|
+
end
|
153
194
|
|
154
|
-
# 9.
|
155
|
-
ranges = Growthbook::Util.get_bucket_ranges(
|
195
|
+
# 9. Get bucket ranges and choose variation
|
196
|
+
ranges = exp.ranges || Growthbook::Util.get_bucket_ranges(
|
156
197
|
exp.variations.length,
|
157
198
|
exp.coverage,
|
158
199
|
exp.weights
|
159
200
|
)
|
160
|
-
n = Growthbook::Util.hash(hash_value
|
201
|
+
n = Growthbook::Util.hash(seed: exp.seed || key, value: hash_value, version: exp.hash_version || 1)
|
161
202
|
assigned = Growthbook::Util.choose_variation(n, ranges)
|
162
203
|
|
163
204
|
# 10. Return if not in experiment
|
164
|
-
return get_experiment_result(exp, -1, false, feature_id) if assigned.negative?
|
205
|
+
return get_experiment_result(exp, -1, hash_used: false, feature_id: feature_id) if assigned.negative?
|
165
206
|
|
166
207
|
# 11. Experiment has a forced variation
|
167
|
-
return get_experiment_result(exp, exp.force, false, feature_id) unless exp.force.nil?
|
208
|
+
return get_experiment_result(exp, exp.force, hash_used: false, feature_id: feature_id) unless exp.force.nil?
|
168
209
|
|
169
210
|
# 12. Exclude if in QA mode
|
170
|
-
return get_experiment_result(exp, -1, false, feature_id) if @qa_mode
|
211
|
+
return get_experiment_result(exp, -1, hash_used: false, feature_id: feature_id) if @qa_mode
|
171
212
|
|
172
213
|
# 13. Build the result object
|
173
|
-
result = get_experiment_result(exp, assigned, true, feature_id)
|
214
|
+
result = get_experiment_result(exp, assigned, hash_used: true, feature_id: feature_id, bucket: n)
|
174
215
|
|
175
216
|
# 14. Fire tracking callback
|
176
217
|
track_experiment(exp, result)
|
@@ -191,7 +232,7 @@ module Growthbook
|
|
191
232
|
Growthbook::Conditions.eval_condition(@attributes, condition)
|
192
233
|
end
|
193
234
|
|
194
|
-
def get_experiment_result(experiment, variation_index = -1, hash_used
|
235
|
+
def get_experiment_result(experiment, variation_index = -1, hash_used: false, feature_id: '', bucket: nil)
|
195
236
|
in_experiment = true
|
196
237
|
if variation_index.negative? || variation_index >= experiment.variations.length
|
197
238
|
variation_index = 0
|
@@ -200,9 +241,17 @@ module Growthbook
|
|
200
241
|
|
201
242
|
hash_attribute = experiment.hash_attribute || 'id'
|
202
243
|
hash_value = get_attribute(hash_attribute)
|
244
|
+
meta = experiment.meta ? experiment.meta[variation_index] : {}
|
203
245
|
|
204
|
-
Growthbook::InlineExperimentResult.new(
|
205
|
-
|
246
|
+
result = Growthbook::InlineExperimentResult.new(
|
247
|
+
{ key: meta['key'] || variation_index, in_experiment: in_experiment, variation_id: variation_index,
|
248
|
+
value: experiment.variations[variation_index], hash_used: hash_used, hash_attribute: hash_attribute,
|
249
|
+
hash_value: hash_value, feature_id: feature_id, bucket: bucket, name: meta['name'] }
|
250
|
+
)
|
251
|
+
|
252
|
+
result.passthrough = true if meta['passthrough']
|
253
|
+
|
254
|
+
result
|
206
255
|
end
|
207
256
|
|
208
257
|
def get_feature_result(value, source, experiment = nil, experiment_result = nil)
|
@@ -224,10 +273,37 @@ module Growthbook
|
|
224
273
|
end
|
225
274
|
|
226
275
|
def track_experiment(experiment, result)
|
227
|
-
|
228
|
-
@listener.on_experiment_viewed(experiment, result)
|
229
|
-
end
|
276
|
+
@listener.on_experiment_viewed(experiment, result) if @listener.respond_to?(:on_experiment_viewed)
|
230
277
|
@impressions[experiment.key] = result
|
231
278
|
end
|
279
|
+
|
280
|
+
def included_in_rollout?(seed:, hash_attribute:, hash_version:, range:, coverage:)
|
281
|
+
return true if range.nil? && coverage.nil?
|
282
|
+
|
283
|
+
hash_value = get_attribute(hash_attribute)
|
284
|
+
|
285
|
+
return false if hash_value.empty?
|
286
|
+
|
287
|
+
n = Growthbook::Util.hash(seed: seed, value: hash_value, version: hash_version || 1)
|
288
|
+
|
289
|
+
return Growthbook::Util.in_range?(n, range) if range
|
290
|
+
return n <= coverage if coverage
|
291
|
+
|
292
|
+
true
|
293
|
+
end
|
294
|
+
|
295
|
+
def filtered_out?(filters)
|
296
|
+
filters.any? do |filter|
|
297
|
+
hash_value = get_attribute(filter['attribute'] || 'id')
|
298
|
+
|
299
|
+
if hash_value.empty?
|
300
|
+
false
|
301
|
+
else
|
302
|
+
n = Growthbook::Util.hash(seed: filter['seed'], value: hash_value, version: filter['hashVersion'] || 2)
|
303
|
+
|
304
|
+
filter['ranges'].none? { |range| Growthbook::Util.in_range?(n, range) }
|
305
|
+
end
|
306
|
+
end
|
307
|
+
end
|
232
308
|
end
|
233
309
|
end
|
data/lib/growthbook/feature.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Growthbook
|
4
|
+
# The feature with a generic value type.
|
4
5
|
class Feature
|
5
6
|
# @return [Any , nil]
|
6
7
|
attr_reader :default_value
|
@@ -9,9 +10,9 @@ module Growthbook
|
|
9
10
|
attr_reader :rules
|
10
11
|
|
11
12
|
def initialize(feature)
|
12
|
-
@default_value =
|
13
|
+
@default_value = get_option(feature, :defaultValue)
|
13
14
|
|
14
|
-
rules =
|
15
|
+
rules = get_option(feature, :rules)
|
15
16
|
|
16
17
|
@rules = []
|
17
18
|
rules&.each do |rule|
|
@@ -31,7 +32,7 @@ module Growthbook
|
|
31
32
|
|
32
33
|
private
|
33
34
|
|
34
|
-
def
|
35
|
+
def get_option(hash, key)
|
35
36
|
return hash[key.to_sym] if hash.key?(key.to_sym)
|
36
37
|
return hash[key.to_s] if hash.key?(key.to_s)
|
37
38
|
|
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Growthbook
|
4
|
+
# Result of a feature evaluation
|
4
5
|
class FeatureResult
|
5
6
|
# The assigned value of the feature
|
6
7
|
# @return [Any, nil]
|
@@ -19,11 +20,11 @@ module Growthbook
|
|
19
20
|
attr_reader :source
|
20
21
|
|
21
22
|
# The experiment used to decide the feature value
|
22
|
-
# @return [Growthbook
|
23
|
+
# @return [Growthbook::InlineExperiment, nil]
|
23
24
|
attr_reader :experiment
|
24
25
|
|
25
26
|
# The result of the experiment
|
26
|
-
# @return [Growthbook
|
27
|
+
# @return [Growthbook::InlineExperimentResult, nil]
|
27
28
|
attr_reader :experiment_result
|
28
29
|
|
29
30
|
def initialize(
|
@@ -1,37 +1,82 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Growthbook
|
4
|
+
# Internal class that overrides the default value of a Feature based on a set of requirements.
|
4
5
|
class FeatureRule
|
5
|
-
# @return [Hash , nil]
|
6
|
+
# @return [Hash , nil] Optional targeting condition
|
6
7
|
attr_reader :condition
|
7
|
-
|
8
|
+
|
9
|
+
# @return [Float , nil] What percent of users should be included in the experiment (between 0 and 1, inclusive)
|
8
10
|
attr_reader :coverage
|
9
|
-
|
11
|
+
|
12
|
+
# @return [T , nil] Immediately force a specific value (ignore every other option besides condition and coverage)
|
10
13
|
attr_reader :force
|
11
|
-
|
14
|
+
|
15
|
+
# @return [T[] , nil] Run an experiment (A/B test) and randomly choose between these variations
|
12
16
|
attr_reader :variations
|
13
|
-
|
17
|
+
|
18
|
+
# @return [String , nil] The globally unique tracking key for the experiment (default to the feature key)
|
14
19
|
attr_reader :key
|
15
|
-
|
20
|
+
|
21
|
+
# @return [Float[] , nil] How to weight traffic between variations. Must add to 1.
|
16
22
|
attr_reader :weights
|
17
|
-
|
23
|
+
|
24
|
+
# @return [String , nil] Adds the experiment to a namespace
|
18
25
|
attr_reader :namespace
|
19
|
-
|
26
|
+
|
27
|
+
# @return [String , nil] What user attribute should be used to assign variations (defaults to id)
|
20
28
|
attr_reader :hash_attribute
|
21
29
|
|
30
|
+
# @return [Integer , nil] The hash version to use (default to 1)
|
31
|
+
attr_reader :hash_version
|
32
|
+
|
33
|
+
# @return [BucketRange , nil] A more precise version of coverage
|
34
|
+
attr_reader :range
|
35
|
+
|
36
|
+
# @return [BucketRanges[] , nil] Ranges for experiment variations
|
37
|
+
attr_reader :ranges
|
38
|
+
|
39
|
+
# @return [VariationMeta[] , nil] Meta info about the experiment variations
|
40
|
+
attr_reader :meta
|
41
|
+
|
42
|
+
# @return [Filter[] , nil] Array of filters to apply to the rule
|
43
|
+
attr_reader :filters
|
44
|
+
|
45
|
+
# @return [String , nil] Seed to use for hashing
|
46
|
+
attr_reader :seed
|
47
|
+
|
48
|
+
# @return [String , nil] Human-readable name for the experiment
|
49
|
+
attr_reader :name
|
50
|
+
|
51
|
+
# @return [String , nil] The phase id of the experiment
|
52
|
+
attr_reader :phase
|
53
|
+
|
54
|
+
# @return [TrackData[] , nil] Array of tracking calls to fire
|
55
|
+
attr_reader :tracks
|
56
|
+
|
22
57
|
def initialize(rule)
|
23
|
-
@coverage =
|
24
|
-
@force =
|
25
|
-
@variations =
|
26
|
-
@key =
|
27
|
-
@weights =
|
28
|
-
@namespace =
|
29
|
-
@hash_attribute =
|
30
|
-
|
31
|
-
|
58
|
+
@coverage = get_option(rule, :coverage)
|
59
|
+
@force = get_option(rule, :force)
|
60
|
+
@variations = get_option(rule, :variations)
|
61
|
+
@key = get_option(rule, :key)
|
62
|
+
@weights = get_option(rule, :weights)
|
63
|
+
@namespace = get_option(rule, :namespace)
|
64
|
+
@hash_attribute = get_option(rule, :hash_attribute) || get_option(rule, :hashAttribute)
|
65
|
+
@hash_version = get_option(rule, :hash_version) || get_option(rule, :hashVersion)
|
66
|
+
@range = get_option(rule, :range)
|
67
|
+
@ranges = get_option(rule, :ranges)
|
68
|
+
@meta = get_option(rule, :meta)
|
69
|
+
@filters = get_option(rule, :filters)
|
70
|
+
@seed = get_option(rule, :seed)
|
71
|
+
@name = get_option(rule, :name)
|
72
|
+
@phase = get_option(rule, :phase)
|
73
|
+
@tracks = get_option(rule, :tracks)
|
74
|
+
|
75
|
+
cond = get_option(rule, :condition)
|
32
76
|
@condition = Growthbook::Conditions.parse_condition(cond) unless cond.nil?
|
33
77
|
end
|
34
78
|
|
79
|
+
# @return [Growthbook::InlineExperiment, nil]
|
35
80
|
def to_experiment(feature_key)
|
36
81
|
return nil unless @variations
|
37
82
|
|
@@ -41,34 +86,49 @@ module Growthbook
|
|
41
86
|
coverage: @coverage,
|
42
87
|
weights: @weights,
|
43
88
|
hash_attribute: @hash_attribute,
|
44
|
-
|
89
|
+
hash_version: @hash_version,
|
90
|
+
namespace: @namespace,
|
91
|
+
meta: @meta,
|
92
|
+
ranges: @ranges,
|
93
|
+
filters: @filters,
|
94
|
+
name: @name,
|
95
|
+
phase: @phase,
|
96
|
+
seed: @seed
|
45
97
|
)
|
46
98
|
end
|
47
99
|
|
48
|
-
def
|
100
|
+
def experiment?
|
49
101
|
!!@variations
|
50
102
|
end
|
51
103
|
|
52
|
-
def
|
53
|
-
!
|
104
|
+
def force?
|
105
|
+
!experiment? && !@force.nil?
|
54
106
|
end
|
55
107
|
|
56
108
|
def to_json(*_args)
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
109
|
+
{
|
110
|
+
'condition' => @condition,
|
111
|
+
'coverage' => @coverage,
|
112
|
+
'force' => @force,
|
113
|
+
'variations' => @variations,
|
114
|
+
'key' => @key,
|
115
|
+
'weights' => @weights,
|
116
|
+
'namespace' => @namespace,
|
117
|
+
'hashAttribute' => @hash_attribute,
|
118
|
+
'range' => @range,
|
119
|
+
'ranges' => @ranges,
|
120
|
+
'meta' => @meta,
|
121
|
+
'filters' => @filters,
|
122
|
+
'seed' => @seed,
|
123
|
+
'name' => @name,
|
124
|
+
'phase' => @phase,
|
125
|
+
'tracks' => @tracks
|
126
|
+
}.compact
|
67
127
|
end
|
68
128
|
|
69
129
|
private
|
70
130
|
|
71
|
-
def
|
131
|
+
def get_option(hash, key)
|
72
132
|
return hash[key.to_sym] if hash.key?(key.to_sym)
|
73
133
|
return hash[key.to_s] if hash.key?(key.to_s)
|
74
134
|
|