featurevisor 2.0.0 → 3.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/README.md +62 -20
- data/bin/cli.rb +2 -2
- data/bin/commands/benchmark.rb +14 -4
- data/bin/commands/test.rb +45 -4
- data/lib/featurevisor/child_instance.rb +64 -289
- data/lib/featurevisor/emitter.rb +1 -1
- data/lib/featurevisor/evaluate.rb +59 -61
- data/lib/featurevisor/evaluation_data_provider.rb +15 -3
- data/lib/featurevisor/events.rb +118 -2
- data/lib/featurevisor/instance.rb +153 -39
- data/lib/featurevisor/modules.rb +51 -6
- data/lib/featurevisor/version.rb +1 -1
- metadata +1 -1
|
@@ -1,351 +1,126 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Featurevisor
|
|
4
|
-
# Child instance
|
|
4
|
+
# Child instance with isolated context and sticky state.
|
|
5
5
|
class ChildInstance
|
|
6
|
-
# Initialize a new child instance
|
|
7
|
-
# @param options [Hash] Child instance options
|
|
8
|
-
# @option options [Instance] :parent Parent instance
|
|
9
|
-
# @option options [Hash] :context Child context
|
|
10
|
-
# @option options [Hash] :sticky Child sticky features
|
|
11
6
|
def initialize(options)
|
|
12
7
|
@parent = options[:parent]
|
|
13
8
|
@context = options[:context] || {}
|
|
14
|
-
@
|
|
9
|
+
@sticky_features = options[:sticky_features] || {}
|
|
10
|
+
@sticky_variables = options[:sticky_variables] || {}
|
|
15
11
|
@emitter = Featurevisor::Emitter.new
|
|
16
12
|
@parent_unsubscribers = []
|
|
17
13
|
end
|
|
18
14
|
|
|
19
|
-
# Subscribe to an event
|
|
20
|
-
# @param event_name [String] Event name
|
|
21
|
-
# @param callback [Proc] Callback function
|
|
22
|
-
# @return [Proc] Unsubscribe function
|
|
23
15
|
def on(event_name, callback = nil, &block)
|
|
24
16
|
callback = block if block_given?
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
17
|
+
if %w[context_set sticky_features_set sticky_variables_set].include?(event_name)
|
|
18
|
+
return @emitter.on(event_name, callback)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
parent_unsubscribe = @parent.on(event_name, callback)
|
|
22
|
+
active = true
|
|
23
|
+
unsubscribe = nil
|
|
24
|
+
unsubscribe = proc do
|
|
25
|
+
next unless active
|
|
34
26
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
end
|
|
39
|
-
@parent_unsubscribers << unsubscribe
|
|
40
|
-
unsubscribe
|
|
27
|
+
active = false
|
|
28
|
+
parent_unsubscribe.call
|
|
29
|
+
@parent_unsubscribers.delete(unsubscribe)
|
|
41
30
|
end
|
|
31
|
+
@parent_unsubscribers << unsubscribe
|
|
32
|
+
unsubscribe
|
|
42
33
|
end
|
|
43
34
|
|
|
44
|
-
# Close the child instance
|
|
45
35
|
def close
|
|
46
36
|
@parent_unsubscribers.dup.each(&:call)
|
|
47
37
|
@parent_unsubscribers.clear
|
|
48
38
|
@emitter.clear_all
|
|
49
39
|
end
|
|
50
40
|
|
|
51
|
-
# Set context
|
|
52
|
-
# @param context [Hash] Context to set
|
|
53
|
-
# @param replace [Boolean] Whether to replace existing context
|
|
54
41
|
def set_context(context, replace = false)
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
else
|
|
58
|
-
@context = { **@context, **context }
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
@emitter.trigger("context_set", {
|
|
62
|
-
context: @context,
|
|
63
|
-
replaced: replace
|
|
64
|
-
})
|
|
42
|
+
@context = replace ? context : { **@context, **context }
|
|
43
|
+
@emitter.trigger("context_set", context: @context, replaced: replace)
|
|
65
44
|
end
|
|
66
45
|
|
|
67
|
-
# Get context
|
|
68
|
-
# @param context [Hash, nil] Additional context to merge
|
|
69
|
-
# @return [Hash] Merged context
|
|
70
46
|
def get_context(context = nil)
|
|
71
|
-
@parent.get_context({
|
|
72
|
-
**@context,
|
|
73
|
-
**(context || {})
|
|
74
|
-
})
|
|
47
|
+
@parent.get_context({ **@context, **(context || {}) })
|
|
75
48
|
end
|
|
76
49
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
if replace
|
|
84
|
-
@sticky = sticky
|
|
85
|
-
else
|
|
86
|
-
@sticky = {
|
|
87
|
-
**@sticky,
|
|
88
|
-
**sticky
|
|
89
|
-
}
|
|
90
|
-
end
|
|
50
|
+
def set_sticky_features(sticky, replace = false)
|
|
51
|
+
previous = @sticky_features
|
|
52
|
+
@sticky_features = replace ? sticky : { **@sticky_features, **sticky }
|
|
53
|
+
params = Featurevisor::Events.get_params_for_sticky_features_set_event(previous, @sticky_features, replace)
|
|
54
|
+
@emitter.trigger("sticky_features_set", params)
|
|
55
|
+
end
|
|
91
56
|
|
|
92
|
-
|
|
93
|
-
@
|
|
57
|
+
def set_sticky_variables(sticky, replace = false)
|
|
58
|
+
previous = @sticky_variables
|
|
59
|
+
@sticky_variables = replace ? sticky : { **@sticky_variables, **sticky }
|
|
60
|
+
@emitter.trigger(
|
|
61
|
+
"sticky_variables_set",
|
|
62
|
+
Featurevisor::Events.get_params_for_sticky_variables_set_event(previous, @sticky_variables, replace)
|
|
63
|
+
)
|
|
94
64
|
end
|
|
95
65
|
|
|
96
|
-
# Check if a feature is enabled
|
|
97
|
-
# @param feature_key [String] Feature key
|
|
98
|
-
# @param context [Hash] Context
|
|
99
|
-
# @param options [Hash] Override options
|
|
100
|
-
# @return [Boolean] True if feature is enabled
|
|
101
66
|
def is_enabled(feature_key, context = {}, options = {})
|
|
102
|
-
@parent.is_enabled(
|
|
103
|
-
feature_key,
|
|
104
|
-
{
|
|
105
|
-
**@context,
|
|
106
|
-
**context
|
|
107
|
-
},
|
|
108
|
-
{
|
|
109
|
-
**options,
|
|
110
|
-
__featurevisor_child_sticky: @sticky
|
|
111
|
-
}
|
|
112
|
-
)
|
|
67
|
+
@parent.is_enabled(feature_key, child_context(context), child_options(options))
|
|
113
68
|
end
|
|
114
69
|
|
|
115
|
-
# Evaluate a feature flag and return its full evaluation details.
|
|
116
70
|
def evaluate_flag(feature_key, context = {}, options = {})
|
|
117
|
-
@parent.evaluate_flag(
|
|
118
|
-
feature_key,
|
|
119
|
-
{ **@context, **context },
|
|
120
|
-
{ **options, __featurevisor_child_sticky: @sticky }
|
|
121
|
-
)
|
|
71
|
+
@parent.evaluate_flag(feature_key, child_context(context), child_options(options))
|
|
122
72
|
end
|
|
123
73
|
|
|
124
|
-
# Get variation value
|
|
125
|
-
# @param feature_key [String] Feature key
|
|
126
|
-
# @param context [Hash] Context
|
|
127
|
-
# @param options [Hash] Override options
|
|
128
|
-
# @return [String, nil] Variation value or nil
|
|
129
74
|
def get_variation(feature_key, context = {}, options = {})
|
|
130
|
-
@parent.get_variation(
|
|
131
|
-
feature_key,
|
|
132
|
-
{
|
|
133
|
-
**@context,
|
|
134
|
-
**context
|
|
135
|
-
},
|
|
136
|
-
{
|
|
137
|
-
**options,
|
|
138
|
-
__featurevisor_child_sticky: @sticky
|
|
139
|
-
}
|
|
140
|
-
)
|
|
75
|
+
@parent.get_variation(feature_key, child_context(context), child_options(options))
|
|
141
76
|
end
|
|
142
77
|
|
|
143
|
-
# Evaluate a variation and return its full evaluation details.
|
|
144
78
|
def evaluate_variation(feature_key, context = {}, options = {})
|
|
145
|
-
@parent.evaluate_variation(
|
|
146
|
-
feature_key,
|
|
147
|
-
{ **@context, **context },
|
|
148
|
-
{ **options, __featurevisor_child_sticky: @sticky }
|
|
149
|
-
)
|
|
150
|
-
end
|
|
151
|
-
|
|
152
|
-
# Get variable value
|
|
153
|
-
# @param feature_key [String] Feature key
|
|
154
|
-
# @param variable_key [String] Variable key
|
|
155
|
-
# @param context [Hash] Context
|
|
156
|
-
# @param options [Hash] Override options
|
|
157
|
-
# @return [Object, nil] Variable value or nil
|
|
158
|
-
def get_variable(feature_key, variable_key, context = {}, options = {})
|
|
159
|
-
@parent.get_variable(
|
|
160
|
-
feature_key,
|
|
161
|
-
variable_key,
|
|
162
|
-
{
|
|
163
|
-
**@context,
|
|
164
|
-
**context
|
|
165
|
-
},
|
|
166
|
-
{
|
|
167
|
-
**options,
|
|
168
|
-
__featurevisor_child_sticky: @sticky
|
|
169
|
-
}
|
|
170
|
-
)
|
|
79
|
+
@parent.evaluate_variation(feature_key, child_context(context), child_options(options))
|
|
171
80
|
end
|
|
172
81
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
@parent.evaluate_variable(
|
|
176
|
-
feature_key,
|
|
177
|
-
variable_key,
|
|
178
|
-
{ **@context, **context },
|
|
179
|
-
{ **options, __featurevisor_child_sticky: @sticky }
|
|
180
|
-
)
|
|
82
|
+
def get_variable(feature_or_variable_key, variable_key_or_context = nil, context_or_options = {}, options = {})
|
|
83
|
+
delegate_variable(:get_variable, feature_or_variable_key, variable_key_or_context, context_or_options, options)
|
|
181
84
|
end
|
|
182
85
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
# @param variable_key [String] Variable key
|
|
186
|
-
# @param context [Hash] Context
|
|
187
|
-
# @param options [Hash] Override options
|
|
188
|
-
# @return [Boolean, nil] Boolean value or nil
|
|
189
|
-
def get_variable_boolean(feature_key, variable_key, context = {}, options = {})
|
|
190
|
-
@parent.get_variable_boolean(
|
|
191
|
-
feature_key,
|
|
192
|
-
variable_key,
|
|
193
|
-
{
|
|
194
|
-
**@context,
|
|
195
|
-
**context
|
|
196
|
-
},
|
|
197
|
-
{
|
|
198
|
-
**options,
|
|
199
|
-
__featurevisor_child_sticky: @sticky
|
|
200
|
-
}
|
|
201
|
-
)
|
|
86
|
+
def evaluate_variable(feature_or_variable_key, variable_key_or_context = nil, context_or_options = {}, options = {})
|
|
87
|
+
delegate_variable(:evaluate_variable, feature_or_variable_key, variable_key_or_context, context_or_options, options)
|
|
202
88
|
end
|
|
203
89
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
# @param options [Hash] Override options
|
|
209
|
-
# @return [String, nil] String value or nil
|
|
210
|
-
def get_variable_string(feature_key, variable_key, context = {}, options = {})
|
|
211
|
-
@parent.get_variable_string(
|
|
212
|
-
feature_key,
|
|
213
|
-
variable_key,
|
|
214
|
-
{
|
|
215
|
-
**@context,
|
|
216
|
-
**context
|
|
217
|
-
},
|
|
218
|
-
{
|
|
219
|
-
**options,
|
|
220
|
-
__featurevisor_child_sticky: @sticky
|
|
221
|
-
}
|
|
222
|
-
)
|
|
90
|
+
%i[boolean string integer double array object json].each do |type|
|
|
91
|
+
define_method("get_variable_#{type}") do |feature_or_variable_key, variable_key_or_context = nil, context_or_options = {}, options = {}|
|
|
92
|
+
delegate_variable("get_variable_#{type}".to_sym, feature_or_variable_key, variable_key_or_context, context_or_options, options)
|
|
93
|
+
end
|
|
223
94
|
end
|
|
224
95
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
# @param variable_key [String] Variable key
|
|
228
|
-
# @param context [Hash] Context
|
|
229
|
-
# @param options [Hash] Override options
|
|
230
|
-
# @return [Integer, nil] Integer value or nil
|
|
231
|
-
def get_variable_integer(feature_key, variable_key, context = {}, options = {})
|
|
232
|
-
@parent.get_variable_integer(
|
|
233
|
-
feature_key,
|
|
234
|
-
variable_key,
|
|
235
|
-
{
|
|
236
|
-
**@context,
|
|
237
|
-
**context
|
|
238
|
-
},
|
|
239
|
-
{
|
|
240
|
-
**options,
|
|
241
|
-
__featurevisor_child_sticky: @sticky
|
|
242
|
-
}
|
|
243
|
-
)
|
|
96
|
+
def get_feature_evaluations(context = {}, feature_keys = [], options = {})
|
|
97
|
+
@parent.get_feature_evaluations(child_context(context), feature_keys, child_options(options))
|
|
244
98
|
end
|
|
245
99
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
# @param variable_key [String] Variable key
|
|
249
|
-
# @param context [Hash] Context
|
|
250
|
-
# @param options [Hash] Override options
|
|
251
|
-
# @return [Float, nil] Float value or nil
|
|
252
|
-
def get_variable_double(feature_key, variable_key, context = {}, options = {})
|
|
253
|
-
@parent.get_variable_double(
|
|
254
|
-
feature_key,
|
|
255
|
-
variable_key,
|
|
256
|
-
{
|
|
257
|
-
**@context,
|
|
258
|
-
**context
|
|
259
|
-
},
|
|
260
|
-
{
|
|
261
|
-
**options,
|
|
262
|
-
__featurevisor_child_sticky: @sticky
|
|
263
|
-
}
|
|
264
|
-
)
|
|
100
|
+
def get_variable_evaluations(context = {}, variable_keys = [], options = {})
|
|
101
|
+
@parent.get_variable_evaluations(child_context(context), variable_keys, child_options(options))
|
|
265
102
|
end
|
|
266
103
|
|
|
267
|
-
|
|
268
|
-
# @param feature_key [String] Feature key
|
|
269
|
-
# @param variable_key [String] Variable key
|
|
270
|
-
# @param context [Hash] Context
|
|
271
|
-
# @param options [Hash] Override options
|
|
272
|
-
# @return [Array, nil] Array value or nil
|
|
273
|
-
def get_variable_array(feature_key, variable_key, context = {}, options = {})
|
|
274
|
-
@parent.get_variable_array(
|
|
275
|
-
feature_key,
|
|
276
|
-
variable_key,
|
|
277
|
-
{
|
|
278
|
-
**@context,
|
|
279
|
-
**context
|
|
280
|
-
},
|
|
281
|
-
{
|
|
282
|
-
**options,
|
|
283
|
-
__featurevisor_child_sticky: @sticky
|
|
284
|
-
}
|
|
285
|
-
)
|
|
286
|
-
end
|
|
104
|
+
private
|
|
287
105
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
# @param variable_key [String] Variable key
|
|
291
|
-
# @param context [Hash] Context
|
|
292
|
-
# @param options [Hash] Override options
|
|
293
|
-
# @return [Hash, nil] Object value or nil
|
|
294
|
-
def get_variable_object(feature_key, variable_key, context = {}, options = {})
|
|
295
|
-
@parent.get_variable_object(
|
|
296
|
-
feature_key,
|
|
297
|
-
variable_key,
|
|
298
|
-
{
|
|
299
|
-
**@context,
|
|
300
|
-
**context
|
|
301
|
-
},
|
|
302
|
-
{
|
|
303
|
-
**options,
|
|
304
|
-
__featurevisor_child_sticky: @sticky
|
|
305
|
-
}
|
|
306
|
-
)
|
|
106
|
+
def child_context(context)
|
|
107
|
+
{ **@context, **context }
|
|
307
108
|
end
|
|
308
109
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
def get_variable_json(feature_key, variable_key, context = {}, options = {})
|
|
316
|
-
@parent.get_variable_json(
|
|
317
|
-
feature_key,
|
|
318
|
-
variable_key,
|
|
319
|
-
{
|
|
320
|
-
**@context,
|
|
321
|
-
**context
|
|
322
|
-
},
|
|
323
|
-
{
|
|
324
|
-
**options,
|
|
325
|
-
__featurevisor_child_sticky: @sticky
|
|
326
|
-
}
|
|
327
|
-
)
|
|
110
|
+
def child_options(options)
|
|
111
|
+
{
|
|
112
|
+
**options,
|
|
113
|
+
__featurevisor_child_sticky_features: @sticky_features,
|
|
114
|
+
__featurevisor_child_sticky_variables: @sticky_variables
|
|
115
|
+
}
|
|
328
116
|
end
|
|
329
117
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
@parent.get_all_evaluations(
|
|
337
|
-
{
|
|
338
|
-
**@context,
|
|
339
|
-
**context
|
|
340
|
-
},
|
|
341
|
-
feature_keys,
|
|
342
|
-
{
|
|
343
|
-
**options,
|
|
344
|
-
__featurevisor_child_sticky: @sticky
|
|
345
|
-
}
|
|
346
|
-
)
|
|
118
|
+
def delegate_variable(method, first, second, third, fourth)
|
|
119
|
+
if second.nil? || second.is_a?(Hash)
|
|
120
|
+
@parent.public_send(method, first, child_context(second || {}), child_options(third))
|
|
121
|
+
else
|
|
122
|
+
@parent.public_send(method, first, second, child_context(third), child_options(fourth))
|
|
123
|
+
end
|
|
347
124
|
end
|
|
348
|
-
|
|
349
|
-
private
|
|
350
125
|
end
|
|
351
126
|
end
|
data/lib/featurevisor/emitter.rb
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
module Featurevisor
|
|
4
4
|
# Event names for the emitter
|
|
5
|
-
EVENT_NAMES = %w[datafile_set context_set
|
|
5
|
+
EVENT_NAMES = %w[datafile_set context_set sticky_features_set sticky_variables_set error].freeze
|
|
6
6
|
|
|
7
7
|
# Event emitter class for handling event subscriptions and triggers
|
|
8
8
|
class Emitter
|
|
@@ -19,6 +19,7 @@ module Featurevisor
|
|
|
19
19
|
VARIABLE_DISABLED = "variable_disabled" # feature is disabled, and variable's disabledValue is used
|
|
20
20
|
VARIABLE_OVERRIDE_VARIATION = "variable_override_variation" # variable overridden from inside a variation
|
|
21
21
|
VARIABLE_OVERRIDE_RULE = "variable_override_rule" # variable overridden from inside a rule
|
|
22
|
+
REQUIRED_FEATURES_UNMET = "required_features_unmet"
|
|
22
23
|
|
|
23
24
|
# Common
|
|
24
25
|
NO_MATCH = "no_match" # no rules matched
|
|
@@ -35,6 +36,50 @@ module Featurevisor
|
|
|
35
36
|
|
|
36
37
|
# Evaluation module for feature flag evaluation
|
|
37
38
|
module Evaluate
|
|
39
|
+
def self.required_features_are_matched(requirements, datafile, options)
|
|
40
|
+
return true if requirements.nil?
|
|
41
|
+
|
|
42
|
+
items = requirements.is_a?(Array) ? requirements : [requirements]
|
|
43
|
+
clean_options = options.reject do |key, _|
|
|
44
|
+
%i[feature_key variable_key default_variation_value default_variable_value].include?(key)
|
|
45
|
+
end
|
|
46
|
+
items.all? do |required|
|
|
47
|
+
if required.is_a?(String)
|
|
48
|
+
key = required
|
|
49
|
+
enabled = true
|
|
50
|
+
variation = nil
|
|
51
|
+
elsif required.key?(:feature)
|
|
52
|
+
key = required[:feature]
|
|
53
|
+
enabled = required.key?(:enabled) ? required[:enabled] : true
|
|
54
|
+
variation = required[:variation]
|
|
55
|
+
else
|
|
56
|
+
key = required[:key]
|
|
57
|
+
enabled = true
|
|
58
|
+
variation = required[:variation]
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
flag = evaluate_with_modules(clean_options.merge(type: "flag", feature_key: key, datafile: datafile))
|
|
62
|
+
next false unless (flag[:enabled] == true) == enabled
|
|
63
|
+
next true if variation.nil?
|
|
64
|
+
|
|
65
|
+
evaluated_variation = evaluate_with_modules(clean_options.merge(type: "variation", feature_key: key, datafile: datafile))
|
|
66
|
+
value = evaluated_variation.key?(:variation_value) ? evaluated_variation[:variation_value] : evaluated_variation.dig(:variation, :value)
|
|
67
|
+
value == variation
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def self.variable_override_matches?(override, datafile, context, options)
|
|
72
|
+
return false unless required_features_are_matched(override[:requiredFeatures], datafile, options)
|
|
73
|
+
|
|
74
|
+
conditions_match = !override[:conditions] || datafile.all_conditions_are_matched(
|
|
75
|
+
datafile.parse_conditions_if_stringified(override[:conditions]), context
|
|
76
|
+
)
|
|
77
|
+
segments_match = !override[:segments] || datafile.all_segments_are_matched(
|
|
78
|
+
datafile.parse_segments_if_stringified(override[:segments]), context
|
|
79
|
+
)
|
|
80
|
+
conditions_match && segments_match &&
|
|
81
|
+
(override.key?(:conditions) || override.key?(:segments) || override.key?(:requiredFeatures))
|
|
82
|
+
end
|
|
38
83
|
|
|
39
84
|
# Evaluate with modules
|
|
40
85
|
# @param options [Hash] Evaluation options
|
|
@@ -53,18 +98,18 @@ module Featurevisor
|
|
|
53
98
|
evaluation = evaluate(result_options)
|
|
54
99
|
|
|
55
100
|
# Default: variation
|
|
56
|
-
if
|
|
101
|
+
if result_options.key?(:default_variation_value) &&
|
|
57
102
|
evaluation[:type] == "variation" &&
|
|
58
103
|
!evaluation.key?(:variation_value) &&
|
|
59
104
|
!evaluation.key?(:variation)
|
|
60
|
-
evaluation[:variation_value] =
|
|
105
|
+
evaluation[:variation_value] = result_options[:default_variation_value]
|
|
61
106
|
end
|
|
62
107
|
|
|
63
108
|
# Default: variable
|
|
64
|
-
if
|
|
109
|
+
if result_options.key?(:default_variable_value) &&
|
|
65
110
|
evaluation[:type] == "variable" &&
|
|
66
111
|
!evaluation.key?(:variable_value)
|
|
67
|
-
evaluation[:variable_value] =
|
|
112
|
+
evaluation[:variable_value] = result_options[:default_variable_value]
|
|
68
113
|
end
|
|
69
114
|
|
|
70
115
|
# Run after modules
|
|
@@ -358,39 +403,9 @@ module Featurevisor
|
|
|
358
403
|
end
|
|
359
404
|
|
|
360
405
|
# Required
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
required_variation = nil
|
|
365
|
-
|
|
366
|
-
if required.is_a?(String)
|
|
367
|
-
required_key = required
|
|
368
|
-
else
|
|
369
|
-
required_key = required[:key]
|
|
370
|
-
required_variation = required[:variation]
|
|
371
|
-
end
|
|
372
|
-
|
|
373
|
-
required_evaluation = evaluate(options.merge(type: "flag", feature_key: required_key))
|
|
374
|
-
required_is_enabled = required_evaluation[:enabled]
|
|
375
|
-
|
|
376
|
-
next false unless required_is_enabled
|
|
377
|
-
|
|
378
|
-
if required_variation
|
|
379
|
-
required_variation_evaluation = evaluate(options.merge(type: "variation", feature_key: required_key))
|
|
380
|
-
|
|
381
|
-
required_variation_value = nil
|
|
382
|
-
|
|
383
|
-
if has_key?(required_variation_evaluation, :variation_value)
|
|
384
|
-
required_variation_value = fetch_with_symbol_key(required_variation_evaluation, :variation_value)
|
|
385
|
-
elsif required_variation_evaluation[:variation]
|
|
386
|
-
required_variation_value = required_variation_evaluation[:variation][:value]
|
|
387
|
-
end
|
|
388
|
-
|
|
389
|
-
next required_variation_value == required_variation
|
|
390
|
-
end
|
|
391
|
-
|
|
392
|
-
true
|
|
393
|
-
end
|
|
406
|
+
required_features = feature[:requiredFeatures] || feature[:required]
|
|
407
|
+
if type == "flag" && required_features && !Array(required_features).empty?
|
|
408
|
+
required_features_are_enabled = required_features_are_matched(required_features, datafile, options)
|
|
394
409
|
|
|
395
410
|
unless required_features_are_enabled
|
|
396
411
|
evaluation = {
|
|
@@ -398,6 +413,7 @@ module Featurevisor
|
|
|
398
413
|
feature_key: feature_key,
|
|
399
414
|
reason: Featurevisor::EvaluationReason::REQUIRED,
|
|
400
415
|
required: feature[:required],
|
|
416
|
+
required_features: feature[:requiredFeatures],
|
|
401
417
|
enabled: required_features_are_enabled
|
|
402
418
|
}
|
|
403
419
|
|
|
@@ -601,17 +617,7 @@ module Featurevisor
|
|
|
601
617
|
has_key?(matched_traffic[:variableOverrides], variable_key)
|
|
602
618
|
overrides = fetch_with_symbol_key(matched_traffic[:variableOverrides], variable_key)
|
|
603
619
|
|
|
604
|
-
override_index = overrides.find_index
|
|
605
|
-
if o[:conditions]
|
|
606
|
-
conditions = o[:conditions].is_a?(String) && o[:conditions] != "*" ? JSON.parse(o[:conditions]) : o[:conditions]
|
|
607
|
-
datafile.all_conditions_are_matched(conditions, context)
|
|
608
|
-
elsif o[:segments]
|
|
609
|
-
segments = datafile.parse_segments_if_stringified(o[:segments])
|
|
610
|
-
datafile.all_segments_are_matched(segments, context)
|
|
611
|
-
else
|
|
612
|
-
false
|
|
613
|
-
end
|
|
614
|
-
end
|
|
620
|
+
override_index = overrides.find_index { |o| variable_override_matches?(o, datafile, context, options) }
|
|
615
621
|
|
|
616
622
|
unless override_index.nil?
|
|
617
623
|
override = overrides[override_index]
|
|
@@ -627,7 +633,8 @@ module Featurevisor
|
|
|
627
633
|
variable_key: variable_key,
|
|
628
634
|
variable_schema: variable_schema,
|
|
629
635
|
variable_value: override[:value],
|
|
630
|
-
variable_override_index: override_index
|
|
636
|
+
variable_override_index: override_index,
|
|
637
|
+
variable_override_key: override[:key]
|
|
631
638
|
}
|
|
632
639
|
|
|
633
640
|
diagnostics.debug("variable override from rule", evaluation)
|
|
@@ -675,17 +682,7 @@ module Featurevisor
|
|
|
675
682
|
if variation && variation[:variableOverrides] && has_key?(variation[:variableOverrides], variable_key)
|
|
676
683
|
overrides = fetch_with_symbol_key(variation[:variableOverrides], variable_key)
|
|
677
684
|
|
|
678
|
-
override_index = overrides.find_index
|
|
679
|
-
if o[:conditions]
|
|
680
|
-
conditions = o[:conditions].is_a?(String) && o[:conditions] != "*" ? JSON.parse(o[:conditions]) : o[:conditions]
|
|
681
|
-
datafile.all_conditions_are_matched(conditions, context)
|
|
682
|
-
elsif o[:segments]
|
|
683
|
-
segments = datafile.parse_segments_if_stringified(o[:segments])
|
|
684
|
-
datafile.all_segments_are_matched(segments, context)
|
|
685
|
-
else
|
|
686
|
-
false
|
|
687
|
-
end
|
|
688
|
-
end
|
|
685
|
+
override_index = overrides.find_index { |o| variable_override_matches?(o, datafile, context, options) }
|
|
689
686
|
|
|
690
687
|
unless override_index.nil?
|
|
691
688
|
override = overrides[override_index]
|
|
@@ -700,7 +697,8 @@ module Featurevisor
|
|
|
700
697
|
variable_key: variable_key,
|
|
701
698
|
variable_schema: variable_schema,
|
|
702
699
|
variable_value: override[:value],
|
|
703
|
-
variable_override_index: override_index
|
|
700
|
+
variable_override_index: override_index,
|
|
701
|
+
variable_override_key: override[:key]
|
|
704
702
|
}
|
|
705
703
|
|
|
706
704
|
diagnostics.debug("variable override from variation", evaluation)
|
|
@@ -5,7 +5,7 @@ require "json"
|
|
|
5
5
|
module Featurevisor
|
|
6
6
|
# Private datafile and matching adapter used by the evaluator.
|
|
7
7
|
class InstanceEvaluationDataProvider
|
|
8
|
-
attr_reader :schema_version, :revision, :featurevisor_version, :segments, :features, :diagnostics, :regex_cache
|
|
8
|
+
attr_reader :schema_version, :revision, :featurevisor_version, :segments, :features, :variables, :diagnostics, :regex_cache
|
|
9
9
|
|
|
10
10
|
# Initialize a new evaluation data provider.
|
|
11
11
|
# @param options [Hash] Options hash containing datafile and diagnostics
|
|
@@ -20,6 +20,7 @@ module Featurevisor
|
|
|
20
20
|
@featurevisor_version = datafile[:featurevisorVersion]
|
|
21
21
|
@segments = (datafile[:segments] || {}).transform_keys(&:to_sym)
|
|
22
22
|
@features = (datafile[:features] || {}).transform_keys(&:to_sym)
|
|
23
|
+
@variables = (datafile[:variables] || {}).transform_keys(&:to_sym)
|
|
23
24
|
|
|
24
25
|
# Transform nested structures to use symbol keys
|
|
25
26
|
@features.each do |_key, feature|
|
|
@@ -72,7 +73,8 @@ module Featurevisor
|
|
|
72
73
|
revision: @revision,
|
|
73
74
|
featurevisorVersion: @featurevisor_version,
|
|
74
75
|
segments: @segments,
|
|
75
|
-
features: @features
|
|
76
|
+
features: @features,
|
|
77
|
+
variables: @variables
|
|
76
78
|
}.compact
|
|
77
79
|
end
|
|
78
80
|
|
|
@@ -88,6 +90,10 @@ module Featurevisor
|
|
|
88
90
|
segment
|
|
89
91
|
end
|
|
90
92
|
|
|
93
|
+
def get_segment_keys
|
|
94
|
+
@segments.keys
|
|
95
|
+
end
|
|
96
|
+
|
|
91
97
|
# Get all feature keys
|
|
92
98
|
# @return [Array<Symbol>] Array of feature keys
|
|
93
99
|
def get_feature_keys
|
|
@@ -104,7 +110,9 @@ module Featurevisor
|
|
|
104
110
|
# Get variable keys for a feature
|
|
105
111
|
# @param feature_key [String] Feature key
|
|
106
112
|
# @return [Array<String>] Array of variable keys
|
|
107
|
-
def get_variable_keys(feature_key)
|
|
113
|
+
def get_variable_keys(feature_key = nil)
|
|
114
|
+
return @variables.keys if feature_key.nil?
|
|
115
|
+
|
|
108
116
|
feature = get_feature(feature_key)
|
|
109
117
|
|
|
110
118
|
return [] unless feature
|
|
@@ -112,6 +120,10 @@ module Featurevisor
|
|
|
112
120
|
(feature[:variablesSchema] || {}).keys
|
|
113
121
|
end
|
|
114
122
|
|
|
123
|
+
def get_global_variable(variable_key)
|
|
124
|
+
@variables[variable_key.to_sym] || @variables[variable_key]
|
|
125
|
+
end
|
|
126
|
+
|
|
115
127
|
# Check if a feature has variations
|
|
116
128
|
# @param feature_key [String] Feature key
|
|
117
129
|
# @return [Boolean] True if feature has variations
|