featurevisor 0.2.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/README.md +172 -75
- data/bin/cli.rb +15 -2
- data/bin/commands/assess_distribution.rb +34 -15
- data/bin/commands/benchmark.rb +65 -63
- data/bin/commands/test.rb +250 -100
- data/lib/featurevisor/child_instance.rb +22 -24
- data/lib/featurevisor/datafile_reader.rb +18 -13
- data/lib/featurevisor/emitter.rb +2 -2
- data/lib/featurevisor/evaluate.rb +106 -100
- data/lib/featurevisor/events.rb +5 -4
- data/lib/featurevisor/instance.rb +225 -39
- data/lib/featurevisor/logger.rb +0 -7
- data/lib/featurevisor/modules.rb +184 -0
- data/lib/featurevisor/version.rb +1 -1
- data/lib/featurevisor.rb +4 -1
- metadata +16 -2
- data/lib/featurevisor/hooks.rb +0 -159
data/bin/commands/test.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
require "json"
|
|
2
2
|
require "find"
|
|
3
3
|
require "open3"
|
|
4
|
+
require "shellwords"
|
|
4
5
|
|
|
5
6
|
module FeaturevisorCLI
|
|
6
7
|
module Commands
|
|
@@ -19,17 +20,18 @@ module FeaturevisorCLI
|
|
|
19
20
|
|
|
20
21
|
# Get project configuration
|
|
21
22
|
config = get_config
|
|
22
|
-
environments = config
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
schema_version = config[:schemaVersion]
|
|
23
|
+
environments = get_environments(config)
|
|
24
|
+
available_targets = get_targets
|
|
25
|
+
unknown_target = @options.targets.find { |target| !available_targets.include?(target) }
|
|
26
|
+
if unknown_target
|
|
27
|
+
puts "Unknown target \"#{unknown_target}\". Available targets: #{available_targets.empty? ? "none" : available_targets.join(", ")}."
|
|
28
|
+
exit 1
|
|
29
29
|
end
|
|
30
|
+
targets = @options.targets.empty? ? available_targets : @options.targets
|
|
31
|
+
segments_by_key = get_segments
|
|
30
32
|
|
|
31
|
-
# Build datafiles for all environments
|
|
32
|
-
|
|
33
|
+
# Build base and Target datafiles for all environments.
|
|
34
|
+
datafiles_by_key = build_datafiles(environments, targets, @options.inflate)
|
|
33
35
|
|
|
34
36
|
puts ""
|
|
35
37
|
|
|
@@ -42,18 +44,15 @@ module FeaturevisorCLI
|
|
|
42
44
|
return
|
|
43
45
|
end
|
|
44
46
|
|
|
45
|
-
# Create SDK instances for each environment
|
|
46
|
-
sdk_instances_by_environment = create_sdk_instances(environments, datafiles_by_environment, level)
|
|
47
|
-
|
|
48
47
|
# Run tests
|
|
49
|
-
run_tests(tests,
|
|
48
|
+
run_tests(tests, datafiles_by_key, segments_by_key, level, config)
|
|
50
49
|
end
|
|
51
50
|
|
|
52
51
|
private
|
|
53
52
|
|
|
54
53
|
def get_config
|
|
55
54
|
puts "Getting config..."
|
|
56
|
-
command = "(cd #{@project_path} && npx featurevisor config --json)"
|
|
55
|
+
command = "(cd #{Shellwords.escape(@project_path)} && npx featurevisor config --json)"
|
|
57
56
|
config_output = execute_command(command)
|
|
58
57
|
|
|
59
58
|
begin
|
|
@@ -67,7 +66,7 @@ module FeaturevisorCLI
|
|
|
67
66
|
|
|
68
67
|
def get_segments
|
|
69
68
|
puts "Getting segments..."
|
|
70
|
-
command = "(cd #{@project_path} && npx featurevisor list --segments --json)"
|
|
69
|
+
command = "(cd #{Shellwords.escape(@project_path)} && npx featurevisor list --segments --json)"
|
|
71
70
|
segments_output = execute_command(command)
|
|
72
71
|
|
|
73
72
|
begin
|
|
@@ -86,40 +85,105 @@ module FeaturevisorCLI
|
|
|
86
85
|
end
|
|
87
86
|
end
|
|
88
87
|
|
|
89
|
-
def
|
|
90
|
-
|
|
88
|
+
def get_targets
|
|
89
|
+
puts "Getting targets..."
|
|
90
|
+
command = "(cd #{Shellwords.escape(@project_path)} && npx featurevisor list --targets --json)"
|
|
91
|
+
targets_output = execute_command(command)
|
|
92
|
+
|
|
93
|
+
begin
|
|
94
|
+
targets = JSON.parse(targets_output, symbolize_names: true)
|
|
95
|
+
|
|
96
|
+
if targets.is_a?(Array)
|
|
97
|
+
targets.filter_map do |target|
|
|
98
|
+
if target.is_a?(Hash)
|
|
99
|
+
target[:name] || target[:key]
|
|
100
|
+
else
|
|
101
|
+
target
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
elsif targets.is_a?(Hash)
|
|
105
|
+
targets.keys
|
|
106
|
+
else
|
|
107
|
+
[]
|
|
108
|
+
end
|
|
109
|
+
rescue JSON::ParserError => e
|
|
110
|
+
puts "Error: Failed to parse targets JSON: #{e.message}"
|
|
111
|
+
puts "Command output: #{targets_output}"
|
|
112
|
+
exit 1
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def get_environments(config)
|
|
117
|
+
environments = config[:environments]
|
|
118
|
+
|
|
119
|
+
return [false] if environments == false
|
|
120
|
+
return environments if environments.is_a?(Array) && !environments.empty?
|
|
121
|
+
|
|
122
|
+
[false]
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def base_datafile_key(environment)
|
|
126
|
+
environment == false ? false : environment
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def target_datafile_key(environment, target)
|
|
130
|
+
if environment == false
|
|
131
|
+
"false-target-#{target}"
|
|
132
|
+
else
|
|
133
|
+
"#{environment}-target-#{target}"
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def build_datafiles(environments, targets, inflate)
|
|
138
|
+
datafiles_by_key = {}
|
|
91
139
|
|
|
92
140
|
environments.each do |environment|
|
|
93
|
-
|
|
141
|
+
environment_label = environment == false ? "default (no environment)" : environment
|
|
142
|
+
puts "Building datafile for environment: #{environment_label}..."
|
|
94
143
|
|
|
95
|
-
|
|
144
|
+
datafiles_by_key[base_datafile_key(environment)] = build_single_datafile(
|
|
145
|
+
environment: environment,
|
|
146
|
+
inflate: inflate
|
|
147
|
+
)
|
|
96
148
|
|
|
97
|
-
|
|
98
|
-
|
|
149
|
+
targets.each do |target|
|
|
150
|
+
puts "Building datafile for target: #{target}..."
|
|
151
|
+
datafiles_by_key[target_datafile_key(environment, target)] = build_single_datafile(
|
|
152
|
+
environment: environment,
|
|
153
|
+
inflate: inflate,
|
|
154
|
+
target: target
|
|
155
|
+
)
|
|
99
156
|
end
|
|
157
|
+
end
|
|
100
158
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
command_parts = ["cd", @project_path, "&&", "npx", "featurevisor", "build", "--environment=#{environment}", "--schemaVersion=#{schema_version}", "--inflate=#{inflate}", "--json"]
|
|
104
|
-
else
|
|
105
|
-
command_parts = ["cd", @project_path, "&&", "npx", "featurevisor", "build", "--environment=#{environment}", "--inflate=#{inflate}", "--json"]
|
|
106
|
-
end
|
|
107
|
-
end
|
|
159
|
+
datafiles_by_key
|
|
160
|
+
end
|
|
108
161
|
|
|
109
|
-
|
|
110
|
-
|
|
162
|
+
def build_single_datafile(environment:, inflate:, target: nil)
|
|
163
|
+
command_parts = ["npx", "featurevisor", "build", "--json"]
|
|
111
164
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
165
|
+
if environment != false && !environment.nil?
|
|
166
|
+
command_parts << "--environment=#{Shellwords.escape(environment.to_s)}"
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
if inflate && inflate > 0
|
|
170
|
+
command_parts << "--inflate=#{inflate}"
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
if target
|
|
174
|
+
command_parts << "--target=#{Shellwords.escape(target.to_s)}"
|
|
120
175
|
end
|
|
121
176
|
|
|
122
|
-
|
|
177
|
+
command = "(cd #{Shellwords.escape(@project_path)} && #{command_parts.join(' ')})"
|
|
178
|
+
datafile_output = execute_command(command)
|
|
179
|
+
|
|
180
|
+
begin
|
|
181
|
+
JSON.parse(datafile_output, symbolize_names: true)
|
|
182
|
+
rescue JSON::ParserError => e
|
|
183
|
+
puts "Error: Failed to parse datafile JSON: #{e.message}"
|
|
184
|
+
puts "Command output: #{datafile_output}"
|
|
185
|
+
exit 1
|
|
186
|
+
end
|
|
123
187
|
end
|
|
124
188
|
|
|
125
189
|
def get_logger_level
|
|
@@ -133,17 +197,17 @@ module FeaturevisorCLI
|
|
|
133
197
|
end
|
|
134
198
|
|
|
135
199
|
def get_tests
|
|
136
|
-
command_parts = ["
|
|
200
|
+
command_parts = ["npx", "featurevisor", "list", "--tests", "--applyMatrix", "--json"]
|
|
137
201
|
|
|
138
202
|
if @options.key_pattern && !@options.key_pattern.empty?
|
|
139
|
-
command_parts << "--keyPattern=#{@options.key_pattern}"
|
|
203
|
+
command_parts << "--keyPattern=#{Shellwords.escape(@options.key_pattern)}"
|
|
140
204
|
end
|
|
141
205
|
|
|
142
206
|
if @options.assertion_pattern && !@options.assertion_pattern.empty?
|
|
143
|
-
command_parts << "--assertionPattern=#{@options.assertion_pattern}"
|
|
207
|
+
command_parts << "--assertionPattern=#{Shellwords.escape(@options.assertion_pattern)}"
|
|
144
208
|
end
|
|
145
209
|
|
|
146
|
-
command = command_parts.join(
|
|
210
|
+
command = "(cd #{Shellwords.escape(@project_path)} && #{command_parts.join(' ')})"
|
|
147
211
|
tests_output = execute_command(command)
|
|
148
212
|
|
|
149
213
|
begin
|
|
@@ -155,31 +219,44 @@ module FeaturevisorCLI
|
|
|
155
219
|
end
|
|
156
220
|
end
|
|
157
221
|
|
|
158
|
-
def
|
|
159
|
-
|
|
222
|
+
def resolve_datafile_for_assertion(assertion, datafiles_by_key)
|
|
223
|
+
environment = assertion.key?(:environment) ? assertion[:environment] : false
|
|
224
|
+
environment = false if environment.nil?
|
|
160
225
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
# Create SDK instance
|
|
165
|
-
instance = Featurevisor.create_instance(
|
|
166
|
-
datafile: datafile,
|
|
167
|
-
log_level: level,
|
|
168
|
-
hooks: [
|
|
169
|
-
{
|
|
170
|
-
name: "tester-hook",
|
|
171
|
-
bucket_value: ->(options) { options.bucket_value }
|
|
172
|
-
}
|
|
173
|
-
]
|
|
174
|
-
)
|
|
226
|
+
target_key = assertion[:target] ? target_datafile_key(environment, assertion[:target]) : nil
|
|
227
|
+
base_key = base_datafile_key(environment)
|
|
175
228
|
|
|
176
|
-
|
|
229
|
+
if target_key && datafiles_by_key.key?(target_key)
|
|
230
|
+
return datafiles_by_key[target_key]
|
|
177
231
|
end
|
|
178
232
|
|
|
179
|
-
|
|
233
|
+
datafiles_by_key[base_key]
|
|
180
234
|
end
|
|
181
235
|
|
|
182
|
-
def
|
|
236
|
+
def create_tester_instance(datafile, level, assertion)
|
|
237
|
+
sticky = parse_sticky(assertion[:sticky])
|
|
238
|
+
|
|
239
|
+
Featurevisor.create_featurevisor(
|
|
240
|
+
datafile: datafile,
|
|
241
|
+
sticky: sticky,
|
|
242
|
+
log_level: level,
|
|
243
|
+
modules: [
|
|
244
|
+
{
|
|
245
|
+
name: "test-module",
|
|
246
|
+
bucket_value: ->(options) do
|
|
247
|
+
at = assertion[:at]
|
|
248
|
+
if at.is_a?(Numeric)
|
|
249
|
+
(at * 1000).to_i
|
|
250
|
+
else
|
|
251
|
+
options[:bucket_value]
|
|
252
|
+
end
|
|
253
|
+
end
|
|
254
|
+
}
|
|
255
|
+
]
|
|
256
|
+
)
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
def run_tests(tests, datafiles_by_key, segments_by_key, level, config)
|
|
183
260
|
passed_tests_count = 0
|
|
184
261
|
failed_tests_count = 0
|
|
185
262
|
passed_assertions_count = 0
|
|
@@ -188,6 +265,12 @@ module FeaturevisorCLI
|
|
|
188
265
|
tests.each do |test|
|
|
189
266
|
test_key = test[:key]
|
|
190
267
|
assertions = test[:assertions] || []
|
|
268
|
+
if test[:feature] && !@options.targets.empty?
|
|
269
|
+
assertions = assertions.select do |assertion|
|
|
270
|
+
assertion[:target].nil? || @options.targets.include?(assertion[:target])
|
|
271
|
+
end
|
|
272
|
+
next if assertions.empty?
|
|
273
|
+
end
|
|
191
274
|
results = ""
|
|
192
275
|
test_has_error = false
|
|
193
276
|
test_duration = 0.0
|
|
@@ -197,43 +280,27 @@ module FeaturevisorCLI
|
|
|
197
280
|
test_result = nil
|
|
198
281
|
|
|
199
282
|
if test[:feature]
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
puts JSON.pretty_generate(datafile)
|
|
208
|
-
puts ""
|
|
283
|
+
datafile = resolve_datafile_for_assertion(assertion, datafiles_by_key)
|
|
284
|
+
if datafile.nil?
|
|
285
|
+
test_result = {
|
|
286
|
+
has_error: true,
|
|
287
|
+
errors: " ✘ no datafile found for assertion target/environment combination\n",
|
|
288
|
+
duration: 0
|
|
289
|
+
}
|
|
209
290
|
end
|
|
210
291
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
datafile = datafiles_by_environment[environment]
|
|
214
|
-
|
|
215
|
-
instance = Featurevisor.create_instance(
|
|
216
|
-
datafile: datafile,
|
|
217
|
-
log_level: level,
|
|
218
|
-
hooks: [
|
|
219
|
-
{
|
|
220
|
-
name: "tester-hook",
|
|
221
|
-
bucket_value: ->(options) do
|
|
222
|
-
# Match JavaScript implementation: assertion.at * (MAX_BUCKETED_NUMBER / 100)
|
|
223
|
-
# MAX_BUCKETED_NUMBER is 100000, so this becomes assertion.at * 1000
|
|
224
|
-
at = assertion[:at]
|
|
225
|
-
if at.is_a?(Numeric)
|
|
226
|
-
(at * 1000).to_i
|
|
227
|
-
else
|
|
228
|
-
options.bucket_value
|
|
229
|
-
end
|
|
230
|
-
end
|
|
231
|
-
}
|
|
232
|
-
]
|
|
233
|
-
)
|
|
234
|
-
end
|
|
292
|
+
if datafile
|
|
293
|
+
instance = create_tester_instance(datafile, level, assertion)
|
|
235
294
|
|
|
236
|
-
|
|
295
|
+
# Show datafile if requested
|
|
296
|
+
if @options.show_datafile
|
|
297
|
+
puts ""
|
|
298
|
+
puts JSON.pretty_generate(datafile)
|
|
299
|
+
puts ""
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
test_result = run_test_feature(assertion, test[:feature], instance, level)
|
|
303
|
+
end
|
|
237
304
|
elsif test[:segment]
|
|
238
305
|
segment_key = test[:segment]
|
|
239
306
|
segment = segments_by_key[segment_key]
|
|
@@ -325,7 +392,7 @@ module FeaturevisorCLI
|
|
|
325
392
|
expected_variables = assertion[:expectedVariables]
|
|
326
393
|
expected_variables.each do |variable_key, expected_value|
|
|
327
394
|
# Set default variable value for this specific variable
|
|
328
|
-
if assertion[:defaultVariableValues] && assertion[:defaultVariableValues]
|
|
395
|
+
if assertion[:defaultVariableValues].is_a?(Hash) && assertion[:defaultVariableValues].key?(variable_key)
|
|
329
396
|
override_options[:default_variable_value] = assertion[:defaultVariableValues][variable_key]
|
|
330
397
|
end
|
|
331
398
|
|
|
@@ -483,7 +550,7 @@ module FeaturevisorCLI
|
|
|
483
550
|
expected_variables = assertion[:expectedVariables]
|
|
484
551
|
expected_variables.each do |variable_key, expected_value|
|
|
485
552
|
# Set default variable value for this specific variable
|
|
486
|
-
if assertion[:defaultVariableValues] && assertion[:defaultVariableValues]
|
|
553
|
+
if assertion[:defaultVariableValues].is_a?(Hash) && assertion[:defaultVariableValues].key?(variable_key)
|
|
487
554
|
override_options[:default_variable_value] = assertion[:defaultVariableValues][variable_key]
|
|
488
555
|
end
|
|
489
556
|
|
|
@@ -522,6 +589,55 @@ module FeaturevisorCLI
|
|
|
522
589
|
end
|
|
523
590
|
end
|
|
524
591
|
|
|
592
|
+
# Test expectedEvaluations
|
|
593
|
+
if assertion[:expectedEvaluations]
|
|
594
|
+
expected_evaluations = assertion[:expectedEvaluations]
|
|
595
|
+
|
|
596
|
+
if expected_evaluations[:flag]
|
|
597
|
+
evaluation = evaluate_from_instance(instance, :flag, feature_key, context, override_options)
|
|
598
|
+
expected_evaluations[:flag].each do |key, expected_value|
|
|
599
|
+
actual_value = get_evaluation_value(evaluation, key)
|
|
600
|
+
if !compare_values(actual_value, expected_value)
|
|
601
|
+
has_error = true
|
|
602
|
+
errors += " ✘ expectedEvaluations.flag.#{key}: expected #{expected_value} but received #{actual_value}\n"
|
|
603
|
+
end
|
|
604
|
+
end
|
|
605
|
+
end
|
|
606
|
+
|
|
607
|
+
if expected_evaluations[:variation]
|
|
608
|
+
evaluation = evaluate_from_instance(instance, :variation, feature_key, context, override_options)
|
|
609
|
+
expected_evaluations[:variation].each do |key, expected_value|
|
|
610
|
+
actual_value = get_evaluation_value(evaluation, key)
|
|
611
|
+
if !compare_values(actual_value, expected_value)
|
|
612
|
+
has_error = true
|
|
613
|
+
errors += " ✘ expectedEvaluations.variation.#{key}: expected #{expected_value} but received #{actual_value}\n"
|
|
614
|
+
end
|
|
615
|
+
end
|
|
616
|
+
end
|
|
617
|
+
|
|
618
|
+
if expected_evaluations[:variables]
|
|
619
|
+
expected_evaluations[:variables].each do |variable_key, expected_eval|
|
|
620
|
+
if expected_eval.is_a?(Hash)
|
|
621
|
+
evaluation = evaluate_from_instance(
|
|
622
|
+
instance,
|
|
623
|
+
:variable,
|
|
624
|
+
feature_key,
|
|
625
|
+
context,
|
|
626
|
+
override_options,
|
|
627
|
+
variable_key
|
|
628
|
+
)
|
|
629
|
+
expected_eval.each do |key, expected_value|
|
|
630
|
+
actual_value = get_evaluation_value(evaluation, key)
|
|
631
|
+
if !compare_values(actual_value, expected_value)
|
|
632
|
+
has_error = true
|
|
633
|
+
errors += " ✘ expectedEvaluations.variables.#{variable_key}.#{key}: expected #{expected_value} but received #{actual_value}\n"
|
|
634
|
+
end
|
|
635
|
+
end
|
|
636
|
+
end
|
|
637
|
+
end
|
|
638
|
+
end
|
|
639
|
+
end
|
|
640
|
+
|
|
525
641
|
duration = Time.now - start_time
|
|
526
642
|
|
|
527
643
|
{
|
|
@@ -544,7 +660,7 @@ module FeaturevisorCLI
|
|
|
544
660
|
}
|
|
545
661
|
|
|
546
662
|
# Create SDK instance for segment testing
|
|
547
|
-
instance = Featurevisor.
|
|
663
|
+
instance = Featurevisor.create_featurevisor(
|
|
548
664
|
datafile: datafile,
|
|
549
665
|
log_level: level
|
|
550
666
|
)
|
|
@@ -614,13 +730,45 @@ module FeaturevisorCLI
|
|
|
614
730
|
def create_override_options(assertion)
|
|
615
731
|
options = {}
|
|
616
732
|
|
|
617
|
-
if assertion
|
|
733
|
+
if assertion.key?(:defaultVariationValue)
|
|
618
734
|
options[:default_variation_value] = assertion[:defaultVariationValue]
|
|
619
735
|
end
|
|
620
736
|
|
|
621
737
|
options
|
|
622
738
|
end
|
|
623
739
|
|
|
740
|
+
def evaluate_from_instance(instance, type, feature_key, context, override_options, variable_key = nil)
|
|
741
|
+
method_name = :"evaluate_#{type}"
|
|
742
|
+
|
|
743
|
+
if instance.respond_to?(method_name)
|
|
744
|
+
if variable_key.nil?
|
|
745
|
+
return instance.send(method_name, feature_key, context, override_options)
|
|
746
|
+
end
|
|
747
|
+
|
|
748
|
+
return instance.send(method_name, feature_key, variable_key, context, override_options)
|
|
749
|
+
end
|
|
750
|
+
|
|
751
|
+
if instance.is_a?(Featurevisor::ChildInstance)
|
|
752
|
+
parent = instance.instance_variable_get(:@parent)
|
|
753
|
+
child_context = instance.instance_variable_get(:@context) || {}
|
|
754
|
+
child_sticky = instance.instance_variable_get(:@sticky)
|
|
755
|
+
combined_context = { **child_context, **context }
|
|
756
|
+
|
|
757
|
+
combined_options = {
|
|
758
|
+
__featurevisor_child_sticky: child_sticky,
|
|
759
|
+
**override_options
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
if variable_key.nil?
|
|
763
|
+
return parent.send(method_name, feature_key, combined_context, combined_options)
|
|
764
|
+
end
|
|
765
|
+
|
|
766
|
+
return parent.send(method_name, feature_key, variable_key, combined_context, combined_options)
|
|
767
|
+
end
|
|
768
|
+
|
|
769
|
+
{}
|
|
770
|
+
end
|
|
771
|
+
|
|
624
772
|
def get_evaluation_value(evaluation, key)
|
|
625
773
|
case key
|
|
626
774
|
when :type
|
|
@@ -659,6 +807,8 @@ module FeaturevisorCLI
|
|
|
659
807
|
evaluation[:variable_value]
|
|
660
808
|
when :variableSchema
|
|
661
809
|
evaluation[:variable_schema]
|
|
810
|
+
when :variableOverrideIndex
|
|
811
|
+
evaluation[:variable_override_index]
|
|
662
812
|
else
|
|
663
813
|
nil
|
|
664
814
|
end
|
|
@@ -3,8 +3,6 @@
|
|
|
3
3
|
module Featurevisor
|
|
4
4
|
# Child instance class for managing child contexts and sticky features
|
|
5
5
|
class ChildInstance
|
|
6
|
-
attr_reader :parent, :context, :sticky, :emitter
|
|
7
|
-
|
|
8
6
|
# Initialize a new child instance
|
|
9
7
|
# @param options [Hash] Child instance options
|
|
10
8
|
# @option options [Instance] :parent Parent instance
|
|
@@ -94,8 +92,8 @@ module Featurevisor
|
|
|
94
92
|
**context
|
|
95
93
|
},
|
|
96
94
|
{
|
|
97
|
-
|
|
98
|
-
|
|
95
|
+
**options,
|
|
96
|
+
__featurevisor_child_sticky: @sticky
|
|
99
97
|
}
|
|
100
98
|
)
|
|
101
99
|
end
|
|
@@ -113,8 +111,8 @@ module Featurevisor
|
|
|
113
111
|
**context
|
|
114
112
|
},
|
|
115
113
|
{
|
|
116
|
-
|
|
117
|
-
|
|
114
|
+
**options,
|
|
115
|
+
__featurevisor_child_sticky: @sticky
|
|
118
116
|
}
|
|
119
117
|
)
|
|
120
118
|
end
|
|
@@ -134,8 +132,8 @@ module Featurevisor
|
|
|
134
132
|
**context
|
|
135
133
|
},
|
|
136
134
|
{
|
|
137
|
-
|
|
138
|
-
|
|
135
|
+
**options,
|
|
136
|
+
__featurevisor_child_sticky: @sticky
|
|
139
137
|
}
|
|
140
138
|
)
|
|
141
139
|
end
|
|
@@ -155,8 +153,8 @@ module Featurevisor
|
|
|
155
153
|
**context
|
|
156
154
|
},
|
|
157
155
|
{
|
|
158
|
-
|
|
159
|
-
|
|
156
|
+
**options,
|
|
157
|
+
__featurevisor_child_sticky: @sticky
|
|
160
158
|
}
|
|
161
159
|
)
|
|
162
160
|
end
|
|
@@ -176,8 +174,8 @@ module Featurevisor
|
|
|
176
174
|
**context
|
|
177
175
|
},
|
|
178
176
|
{
|
|
179
|
-
|
|
180
|
-
|
|
177
|
+
**options,
|
|
178
|
+
__featurevisor_child_sticky: @sticky
|
|
181
179
|
}
|
|
182
180
|
)
|
|
183
181
|
end
|
|
@@ -197,8 +195,8 @@ module Featurevisor
|
|
|
197
195
|
**context
|
|
198
196
|
},
|
|
199
197
|
{
|
|
200
|
-
|
|
201
|
-
|
|
198
|
+
**options,
|
|
199
|
+
__featurevisor_child_sticky: @sticky
|
|
202
200
|
}
|
|
203
201
|
)
|
|
204
202
|
end
|
|
@@ -218,8 +216,8 @@ module Featurevisor
|
|
|
218
216
|
**context
|
|
219
217
|
},
|
|
220
218
|
{
|
|
221
|
-
|
|
222
|
-
|
|
219
|
+
**options,
|
|
220
|
+
__featurevisor_child_sticky: @sticky
|
|
223
221
|
}
|
|
224
222
|
)
|
|
225
223
|
end
|
|
@@ -239,8 +237,8 @@ module Featurevisor
|
|
|
239
237
|
**context
|
|
240
238
|
},
|
|
241
239
|
{
|
|
242
|
-
|
|
243
|
-
|
|
240
|
+
**options,
|
|
241
|
+
__featurevisor_child_sticky: @sticky
|
|
244
242
|
}
|
|
245
243
|
)
|
|
246
244
|
end
|
|
@@ -260,8 +258,8 @@ module Featurevisor
|
|
|
260
258
|
**context
|
|
261
259
|
},
|
|
262
260
|
{
|
|
263
|
-
|
|
264
|
-
|
|
261
|
+
**options,
|
|
262
|
+
__featurevisor_child_sticky: @sticky
|
|
265
263
|
}
|
|
266
264
|
)
|
|
267
265
|
end
|
|
@@ -281,8 +279,8 @@ module Featurevisor
|
|
|
281
279
|
**context
|
|
282
280
|
},
|
|
283
281
|
{
|
|
284
|
-
|
|
285
|
-
|
|
282
|
+
**options,
|
|
283
|
+
__featurevisor_child_sticky: @sticky
|
|
286
284
|
}
|
|
287
285
|
)
|
|
288
286
|
end
|
|
@@ -300,8 +298,8 @@ module Featurevisor
|
|
|
300
298
|
},
|
|
301
299
|
feature_keys,
|
|
302
300
|
{
|
|
303
|
-
|
|
304
|
-
|
|
301
|
+
**options,
|
|
302
|
+
__featurevisor_child_sticky: @sticky
|
|
305
303
|
}
|
|
306
304
|
)
|
|
307
305
|
end
|