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
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: acf664663389bb7b3789a16f0e29c84aeb13e623f26bb38465e3bc8a8dcc984e
|
|
4
|
+
data.tar.gz: 0ab04bbea29092bcfd22361bda28ab6e36879b346d579b5a204780a3bbfb2eaa
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e123cd15ff03b6230ca26de9cdee003414b59f5b075a116b946f7b90562123980f083a4c670748d71cefcdcee0c96197e13fceb6d8a8b6bd841d0cd419167ba6
|
|
7
|
+
data.tar.gz: ef355cc9d0f2e6660510545c9f78ccefd031c71a9a52646d36ec28fe4bac1c5d38e18c8b6f2b1d9768f54f695fa7360c5e69e2d89ff9ba2cf0fa877d91a96a56
|
data/README.md
CHANGED
|
@@ -19,8 +19,9 @@ This SDK is compatible with Featurevisor v3 projects and v2 datafiles.
|
|
|
19
19
|
- [Getting variation](#getting-variation)
|
|
20
20
|
- [Getting variables](#getting-variables)
|
|
21
21
|
- [Type specific methods](#type-specific-methods)
|
|
22
|
-
- [Getting
|
|
23
|
-
- [
|
|
22
|
+
- [Getting global variables](#getting-global-variables)
|
|
23
|
+
- [Getting aggregate evaluations](#getting-aggregate-evaluations)
|
|
24
|
+
- [Sticky features and variables](#sticky-features-and-variables)
|
|
24
25
|
- [Initialize with sticky](#initialize-with-sticky)
|
|
25
26
|
- [Set sticky afterwards](#set-sticky-afterwards)
|
|
26
27
|
- [Setting datafile](#setting-datafile)
|
|
@@ -36,7 +37,7 @@ This SDK is compatible with Featurevisor v3 projects and v2 datafiles.
|
|
|
36
37
|
- [Events](#events)
|
|
37
38
|
- [`datafile_set`](#datafile_set)
|
|
38
39
|
- [`context_set`](#context_set)
|
|
39
|
-
- [`
|
|
40
|
+
- [`sticky_features_set` and `sticky_variables_set`](#sticky_features_set-and-sticky_variables_set)
|
|
40
41
|
- [`error`](#error)
|
|
41
42
|
- [Modules](#modules)
|
|
42
43
|
- [Defining a module](#defining-a-module)
|
|
@@ -89,7 +90,7 @@ f = Featurevisor.create_featurevisor(
|
|
|
89
90
|
|
|
90
91
|
Most applications only need this factory and the returned `Featurevisor::Instance`. Public extension and observability APIs include modules, diagnostics, events, and the datafile structures accepted by the factory.
|
|
91
92
|
|
|
92
|
-
Concurrent evaluations are safe after an instance is configured. Do not mutate or close the same instance concurrently with evaluations. Serialize calls to `set_datafile`, `set_context`, `
|
|
93
|
+
Concurrent evaluations are safe after an instance is configured. Do not mutate or close the same instance concurrently with evaluations. Serialize calls to `set_datafile`, `set_context`, `set_sticky_features`, `set_sticky_variables`, `add_module`, `remove_module`, and `close`. Module, event, and diagnostic callbacks must synchronize mutable state that they capture.
|
|
93
94
|
|
|
94
95
|
## Initialization
|
|
95
96
|
|
|
@@ -295,12 +296,24 @@ f.get_variable_json(feature_key, variable_key, context = {})
|
|
|
295
296
|
|
|
296
297
|
Type specific methods do not coerce values. `get_variable_integer` returns `nil` for the string `"1"`, and boolean getters return `nil` for non-boolean values.
|
|
297
298
|
|
|
298
|
-
## Getting
|
|
299
|
+
## Getting global variables
|
|
300
|
+
|
|
301
|
+
Global variables use the same methods with a single variable key:
|
|
302
|
+
|
|
303
|
+
```ruby
|
|
304
|
+
message = f.get_variable_string('welcomeMessage', context)
|
|
305
|
+
value = f.get_variable('checkoutSettings', context)
|
|
306
|
+
evaluation = f.evaluate_variable('checkoutSettings', context)
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
Global variables resolve sticky values first, then required features, then the first matching override, and finally their default value.
|
|
310
|
+
|
|
311
|
+
## Getting aggregate evaluations
|
|
299
312
|
|
|
300
313
|
You can get evaluations of all features available in the SDK instance:
|
|
301
314
|
|
|
302
315
|
```ruby
|
|
303
|
-
all_evaluations = f.
|
|
316
|
+
all_evaluations = f.get_feature_evaluations({})
|
|
304
317
|
|
|
305
318
|
puts all_evaluations
|
|
306
319
|
# {
|
|
@@ -321,11 +334,17 @@ puts all_evaluations
|
|
|
321
334
|
|
|
322
335
|
This is handy especially when you want to pass all evaluations from a backend application to the frontend.
|
|
323
336
|
|
|
324
|
-
|
|
337
|
+
Global variable values are available separately:
|
|
338
|
+
|
|
339
|
+
```ruby
|
|
340
|
+
global_variables = f.get_variable_evaluations({})
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
## Sticky features and variables
|
|
325
344
|
|
|
326
345
|
For the lifecycle of the SDK instance in your application, you can set some features with sticky values, meaning that they will not be evaluated against the fetched [datafile](https://featurevisor.com/docs/building-datafiles/):
|
|
327
346
|
|
|
328
|
-
Sticky values belong to an SDK or child instance.
|
|
347
|
+
Sticky values belong to an SDK or child instance. Feature and global variable sticky values are independent.
|
|
329
348
|
|
|
330
349
|
### Initialize with sticky
|
|
331
350
|
|
|
@@ -333,7 +352,7 @@ Sticky values belong to an SDK or child instance. Evaluation options do not acce
|
|
|
333
352
|
require 'featurevisor'
|
|
334
353
|
|
|
335
354
|
f = Featurevisor.create_featurevisor(
|
|
336
|
-
|
|
355
|
+
sticky_features: {
|
|
337
356
|
myFeatureKey: {
|
|
338
357
|
enabled: true,
|
|
339
358
|
# optional
|
|
@@ -345,6 +364,9 @@ f = Featurevisor.create_featurevisor(
|
|
|
345
364
|
anotherFeatureKey: {
|
|
346
365
|
enabled: false
|
|
347
366
|
}
|
|
367
|
+
},
|
|
368
|
+
sticky_variables: {
|
|
369
|
+
welcomeMessage: 'Welcome back'
|
|
348
370
|
}
|
|
349
371
|
)
|
|
350
372
|
```
|
|
@@ -356,7 +378,7 @@ Once initialized with sticky features, the SDK will look for values there first
|
|
|
356
378
|
You can also set sticky features after the SDK is initialized:
|
|
357
379
|
|
|
358
380
|
```ruby
|
|
359
|
-
f.
|
|
381
|
+
f.set_sticky_features({
|
|
360
382
|
myFeatureKey: {
|
|
361
383
|
enabled: true,
|
|
362
384
|
variation: 'treatment',
|
|
@@ -368,6 +390,8 @@ f.set_sticky({
|
|
|
368
390
|
enabled: false
|
|
369
391
|
}
|
|
370
392
|
}, true) # replace existing sticky features (false by default)
|
|
393
|
+
|
|
394
|
+
f.set_sticky_variables({ welcomeMessage: 'Welcome back' }, true)
|
|
371
395
|
```
|
|
372
396
|
|
|
373
397
|
## Setting datafile
|
|
@@ -393,7 +417,7 @@ By default, `set_datafile(datafile)` merges the incoming datafile into the SDK's
|
|
|
393
417
|
- `segments` are merged, with incoming entries overriding existing ones
|
|
394
418
|
- `features` are merged, with incoming entries overriding existing ones
|
|
395
419
|
|
|
396
|
-
This means you can call `set_datafile` more than once with different datafiles, and the SDK instance accumulates their features and
|
|
420
|
+
This means you can call `set_datafile` more than once with different datafiles, and the SDK instance accumulates their features, segments, and global variables together.
|
|
397
421
|
|
|
398
422
|
### Replacing
|
|
399
423
|
|
|
@@ -545,15 +569,19 @@ unsubscribe = f.on('context_set') do |event|
|
|
|
545
569
|
end
|
|
546
570
|
```
|
|
547
571
|
|
|
548
|
-
### `
|
|
572
|
+
### `sticky_features_set` and `sticky_variables_set`
|
|
549
573
|
|
|
550
574
|
```ruby
|
|
551
|
-
|
|
575
|
+
feature_unsubscribe = f.on('sticky_features_set') do |event|
|
|
552
576
|
replaced = event[:replaced] # true if sticky features got replaced
|
|
553
577
|
features = event[:features] # list of all affected feature keys
|
|
554
578
|
|
|
555
579
|
puts 'Sticky features set'
|
|
556
580
|
end
|
|
581
|
+
|
|
582
|
+
variable_unsubscribe = f.on('sticky_variables_set') do |event|
|
|
583
|
+
variables = event[:variables]
|
|
584
|
+
end
|
|
557
585
|
```
|
|
558
586
|
|
|
559
587
|
### `error`
|
|
@@ -583,11 +611,14 @@ evaluation = f.evaluate_variation(feature_key, context = {})
|
|
|
583
611
|
|
|
584
612
|
# variable
|
|
585
613
|
evaluation = f.evaluate_variable(feature_key, variable_key, context = {})
|
|
614
|
+
|
|
615
|
+
# global variable
|
|
616
|
+
global_evaluation = f.evaluate_variable(variable_key, context = {})
|
|
586
617
|
```
|
|
587
618
|
|
|
588
619
|
The returned object will always contain the following properties:
|
|
589
620
|
|
|
590
|
-
- `feature_key`: the feature key
|
|
621
|
+
- `feature_key`: the feature key when evaluating a feature
|
|
591
622
|
- `reason`: the reason how the value was evaluated
|
|
592
623
|
|
|
593
624
|
And optionally these properties depending on whether you are evaluating a feature variation or a variable:
|
|
@@ -606,6 +637,8 @@ And optionally these properties depending on whether you are evaluating a featur
|
|
|
606
637
|
|
|
607
638
|
Modules allow you to intercept the evaluation process and customize SDK behavior.
|
|
608
639
|
|
|
640
|
+
For feature evaluations, all `before` callbacks run in registration order, followed by all `before_evaluation` callbacks. After evaluation and caller defaults, all `after_evaluation` callbacks run, followed by all `after` callbacks. Global variable evaluations use only `before_evaluation` and `after_evaluation`. Required feature checks run through the complete module pipeline, and transformed defaults are preserved.
|
|
641
|
+
|
|
609
642
|
### Defining a module
|
|
610
643
|
|
|
611
644
|
A module is a simple hash with a unique recommended `name` and optional lifecycle functions:
|
|
@@ -639,6 +672,9 @@ my_custom_module = {
|
|
|
639
672
|
options
|
|
640
673
|
},
|
|
641
674
|
|
|
675
|
+
# unified callback for feature and global variable evaluations
|
|
676
|
+
before_evaluation: ->(options) { options },
|
|
677
|
+
|
|
642
678
|
# after evaluation
|
|
643
679
|
after: ->(evaluation, options) {
|
|
644
680
|
reason = evaluation[:reason]
|
|
@@ -648,6 +684,9 @@ my_custom_module = {
|
|
|
648
684
|
end
|
|
649
685
|
},
|
|
650
686
|
|
|
687
|
+
# unified callback for feature and global variable evaluations
|
|
688
|
+
after_evaluation: ->(evaluation, options) { evaluation },
|
|
689
|
+
|
|
651
690
|
# configure bucket key
|
|
652
691
|
bucket_key: ->(options) {
|
|
653
692
|
# return custom bucket key
|
|
@@ -720,12 +759,14 @@ Now you can pass the child instance where your individual request is being handl
|
|
|
720
759
|
is_enabled = child_f.is_enabled('my_feature')
|
|
721
760
|
variation = child_f.get_variation('my_feature')
|
|
722
761
|
variable_value = child_f.get_variable('my_feature', 'my_variable')
|
|
762
|
+
global_value = child_f.get_variable('welcomeMessage')
|
|
723
763
|
```
|
|
724
764
|
|
|
725
765
|
Similar to parent SDK, child instances also support several additional methods:
|
|
726
766
|
|
|
727
767
|
- `set_context`
|
|
728
|
-
- `
|
|
768
|
+
- `set_sticky_features`
|
|
769
|
+
- `set_sticky_variables`
|
|
729
770
|
- `evaluate_flag`
|
|
730
771
|
- `is_enabled`
|
|
731
772
|
- `evaluate_variation`
|
|
@@ -739,7 +780,8 @@ Similar to parent SDK, child instances also support several additional methods:
|
|
|
739
780
|
- `get_variable_array`
|
|
740
781
|
- `get_variable_object`
|
|
741
782
|
- `get_variable_json`
|
|
742
|
-
- `
|
|
783
|
+
- `get_feature_evaluations`
|
|
784
|
+
- `get_variable_evaluations`
|
|
743
785
|
- `on`
|
|
744
786
|
- `close`
|
|
745
787
|
|
|
@@ -828,7 +870,7 @@ The provider currently requires Ruby 3.4 or newer because that is the minimum ve
|
|
|
828
870
|
Install the provider:
|
|
829
871
|
|
|
830
872
|
```ruby
|
|
831
|
-
gem "featurevisor-openfeature", "~>
|
|
873
|
+
gem "featurevisor-openfeature", "~> 3.0"
|
|
832
874
|
```
|
|
833
875
|
|
|
834
876
|
It installs the matching `featurevisor` gem and the official `openfeature-sdk` dependency. The provider and base SDK deliberately share the same version, and the provider requires that exact Featurevisor version.
|
|
@@ -852,9 +894,9 @@ enabled = client.fetch_boolean_value(
|
|
|
852
894
|
)
|
|
853
895
|
```
|
|
854
896
|
|
|
855
|
-
Use `checkout` for a flag, `checkout:variation` for its variation,
|
|
897
|
+
Use `checkout` for a flag, `checkout:variation` for its variation, `checkout:title` for its `title` variable, and `variable:welcomeMessage` for a global variable. Boolean variables use the boolean resolver. Arrays, hashes, and JSON variables use the object resolver.
|
|
856
898
|
|
|
857
|
-
OpenFeature's targeting key maps to `userId` by default. `targeting_key_field`, `key_separator`, and `
|
|
899
|
+
OpenFeature's targeting key maps to `userId` by default. `targeting_key_field`, `key_separator`, `variation_key`, and `global_variable_prefix` can customize the mapping. The global variable prefix defaults to `variable` and cannot contain the separator.
|
|
858
900
|
|
|
859
901
|
You can pass any Featurevisor initialization options directly to the provider. These options are used to create the Featurevisor instance owned by the provider:
|
|
860
902
|
|
|
@@ -908,7 +950,7 @@ The build produces `featurevisor-VERSION.gem` and `featurevisor-openfeature-VERS
|
|
|
908
950
|
- Run `bundle install`
|
|
909
951
|
- Push commit to `main` branch
|
|
910
952
|
- Wait for CI to complete
|
|
911
|
-
- Tag the release with the same version number, for example `
|
|
953
|
+
- Tag the release with the same version number, for example `v3.0.0`
|
|
912
954
|
- The workflow verifies that the tag matches the shared version
|
|
913
955
|
- The workflow publishes `featurevisor` first, followed by `featurevisor-openfeature`
|
|
914
956
|
|
data/bin/cli.rb
CHANGED
|
@@ -43,7 +43,7 @@ module FeaturevisorCLI
|
|
|
43
43
|
options.environment = v
|
|
44
44
|
end
|
|
45
45
|
|
|
46
|
-
opts.on("--feature=FEATURE", "Feature key
|
|
46
|
+
opts.on("--feature=FEATURE", "Feature key") do |v|
|
|
47
47
|
options.feature = v
|
|
48
48
|
end
|
|
49
49
|
|
|
@@ -150,6 +150,6 @@ module FeaturevisorCLI
|
|
|
150
150
|
puts " featurevisor benchmark --feature=myFeature --environment=dev --n=10000"
|
|
151
151
|
puts " featurevisor assess-distribution --feature=myFeature --n=10000"
|
|
152
152
|
puts ""
|
|
153
|
-
puts "Note: benchmark
|
|
153
|
+
puts "Note: benchmark requires --environment and either --feature or --variable"
|
|
154
154
|
end
|
|
155
155
|
end
|
data/bin/commands/benchmark.rb
CHANGED
|
@@ -22,8 +22,8 @@ module FeaturevisorCLI
|
|
|
22
22
|
exit 1
|
|
23
23
|
end
|
|
24
24
|
|
|
25
|
-
unless @options.feature
|
|
26
|
-
puts "Error: --feature is required for benchmark command"
|
|
25
|
+
unless @options.feature || @options.variable
|
|
26
|
+
puts "Error: --feature or --variable is required for benchmark command"
|
|
27
27
|
exit 1
|
|
28
28
|
end
|
|
29
29
|
|
|
@@ -41,7 +41,8 @@ module FeaturevisorCLI
|
|
|
41
41
|
datafile_build_duration_ms = (datafile_build_duration * 1000).round
|
|
42
42
|
|
|
43
43
|
puts "\nBenchmark Featurevisor feature"
|
|
44
|
-
puts " Feature: #{@options.feature}"
|
|
44
|
+
puts " Feature: #{@options.feature}" if @options.feature
|
|
45
|
+
puts " Global variable: #{@options.variable}" if @options.variable && !@options.feature
|
|
45
46
|
puts " Environment: #{@options.environment}"
|
|
46
47
|
puts " Target: #{target}" if target
|
|
47
48
|
puts " Iterations: #{@options.n}"
|
|
@@ -59,7 +60,10 @@ module FeaturevisorCLI
|
|
|
59
60
|
puts "Against context: #{context.to_json}"
|
|
60
61
|
|
|
61
62
|
# Run the appropriate benchmark
|
|
62
|
-
if @options.
|
|
63
|
+
if @options.variable && !@options.feature
|
|
64
|
+
puts "Evaluating global variable \"#{@options.variable}\" #{@options.n} times..."
|
|
65
|
+
output = benchmark_global_variable(instance, @options.variable, context, @options.n)
|
|
66
|
+
elsif @options.variation
|
|
63
67
|
puts "Evaluating variation #{@options.n} times..."
|
|
64
68
|
output = benchmark_feature_variation(instance, @options.feature, context, @options.n)
|
|
65
69
|
elsif @options.variable
|
|
@@ -223,6 +227,12 @@ module FeaturevisorCLI
|
|
|
223
227
|
end
|
|
224
228
|
end
|
|
225
229
|
|
|
230
|
+
def benchmark_global_variable(instance, variable_key, context, n)
|
|
231
|
+
benchmark_evaluation(n) do
|
|
232
|
+
instance.get_variable(variable_key, context)
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
|
|
226
236
|
def format_value(value)
|
|
227
237
|
if value.nil?
|
|
228
238
|
"null"
|
data/bin/commands/test.rb
CHANGED
|
@@ -235,10 +235,12 @@ module FeaturevisorCLI
|
|
|
235
235
|
|
|
236
236
|
def create_tester_instance(datafile, level, assertion)
|
|
237
237
|
sticky = parse_sticky(assertion[:sticky])
|
|
238
|
+
sticky_variables = assertion[:stickyVariables].is_a?(Hash) ? assertion[:stickyVariables] : {}
|
|
238
239
|
|
|
239
240
|
Featurevisor.create_featurevisor(
|
|
240
241
|
datafile: datafile,
|
|
241
|
-
|
|
242
|
+
sticky_features: sticky,
|
|
243
|
+
sticky_variables: sticky_variables,
|
|
242
244
|
log_level: level,
|
|
243
245
|
modules: [
|
|
244
246
|
{
|
|
@@ -265,7 +267,7 @@ module FeaturevisorCLI
|
|
|
265
267
|
tests.each do |test|
|
|
266
268
|
test_key = test[:key]
|
|
267
269
|
assertions = test[:assertions] || []
|
|
268
|
-
if test[:feature] && !@options.targets.empty?
|
|
270
|
+
if (test[:feature] || test[:variable]) && !@options.targets.empty?
|
|
269
271
|
assertions = assertions.select do |assertion|
|
|
270
272
|
assertion[:target].nil? || @options.targets.include?(assertion[:target])
|
|
271
273
|
end
|
|
@@ -301,6 +303,14 @@ module FeaturevisorCLI
|
|
|
301
303
|
|
|
302
304
|
test_result = run_test_feature(assertion, test[:feature], instance, level)
|
|
303
305
|
end
|
|
306
|
+
elsif test[:variable]
|
|
307
|
+
datafile = resolve_datafile_for_assertion(assertion, datafiles_by_key)
|
|
308
|
+
if datafile
|
|
309
|
+
instance = create_tester_instance(datafile, level, assertion)
|
|
310
|
+
test_result = run_test_variable(assertion, test[:variable], instance)
|
|
311
|
+
else
|
|
312
|
+
test_result = { has_error: true, errors: " ✘ no datafile found for assertion target/environment combination\n", duration: 0 }
|
|
313
|
+
end
|
|
304
314
|
elsif test[:segment]
|
|
305
315
|
segment_key = test[:segment]
|
|
306
316
|
segment = segments_by_key[segment_key]
|
|
@@ -347,6 +357,31 @@ module FeaturevisorCLI
|
|
|
347
357
|
end
|
|
348
358
|
end
|
|
349
359
|
|
|
360
|
+
def run_test_variable(assertion, variable_key, instance)
|
|
361
|
+
context = parse_context(assertion[:context])
|
|
362
|
+
options = {}
|
|
363
|
+
options[:default_variable_value] = assertion[:defaultVariableValue] if assertion.key?(:defaultVariableValue)
|
|
364
|
+
started = Time.now
|
|
365
|
+
errors = ""
|
|
366
|
+
|
|
367
|
+
if assertion.key?(:expectedValue)
|
|
368
|
+
actual = instance.get_variable(variable_key, context, options)
|
|
369
|
+
unless compare_values(actual, assertion[:expectedValue])
|
|
370
|
+
errors += " ✘ expectedValue: expected #{assertion[:expectedValue].inspect} but received #{actual.inspect}\n"
|
|
371
|
+
end
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
if assertion[:expectedEvaluation].is_a?(Hash)
|
|
375
|
+
evaluation = instance.evaluate_variable(variable_key, context, options)
|
|
376
|
+
assertion[:expectedEvaluation].each do |key, expected|
|
|
377
|
+
actual = get_evaluation_value(evaluation, key)
|
|
378
|
+
errors += " ✘ expectedEvaluation.#{key}: expected #{expected.inspect} but received #{actual.inspect}\n" unless compare_values(actual, expected)
|
|
379
|
+
end
|
|
380
|
+
end
|
|
381
|
+
|
|
382
|
+
{ has_error: !errors.empty?, errors: errors, duration: Time.now - started }
|
|
383
|
+
end
|
|
384
|
+
|
|
350
385
|
def run_test_feature(assertion, feature_key, instance, level)
|
|
351
386
|
context = parse_context(assertion[:context])
|
|
352
387
|
sticky = parse_sticky(assertion[:sticky])
|
|
@@ -354,7 +389,7 @@ module FeaturevisorCLI
|
|
|
354
389
|
# Set context and sticky for this assertion
|
|
355
390
|
instance.set_context(context, false)
|
|
356
391
|
if sticky && !sticky.empty?
|
|
357
|
-
instance.
|
|
392
|
+
instance.set_sticky_features(sticky, false)
|
|
358
393
|
end
|
|
359
394
|
|
|
360
395
|
# Create override options
|
|
@@ -492,7 +527,7 @@ module FeaturevisorCLI
|
|
|
492
527
|
# Create a local copy to ensure it's never nil
|
|
493
528
|
child_sticky = sticky || {}
|
|
494
529
|
if !child_sticky.empty?
|
|
495
|
-
child_instance.
|
|
530
|
+
child_instance.set_sticky_features(child_sticky, false)
|
|
496
531
|
end
|
|
497
532
|
|
|
498
533
|
child_result = run_test_feature_child(child, feature_key, child_instance, level)
|
|
@@ -795,6 +830,8 @@ module FeaturevisorCLI
|
|
|
795
830
|
evaluation[:force]
|
|
796
831
|
when :required
|
|
797
832
|
evaluation[:required]
|
|
833
|
+
when :requiredFeatures
|
|
834
|
+
evaluation[:required_features]
|
|
798
835
|
when :sticky
|
|
799
836
|
evaluation[:sticky]
|
|
800
837
|
when :variation
|
|
@@ -809,6 +846,10 @@ module FeaturevisorCLI
|
|
|
809
846
|
evaluation[:variable_schema]
|
|
810
847
|
when :variableOverrideIndex
|
|
811
848
|
evaluation[:variable_override_index]
|
|
849
|
+
when :variableOverrideKey
|
|
850
|
+
evaluation[:variable_override_key]
|
|
851
|
+
when :variableOverridePath
|
|
852
|
+
evaluation[:variable_override_path]
|
|
812
853
|
else
|
|
813
854
|
nil
|
|
814
855
|
end
|