featurevisor 0.3.0 → 1.1.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 +234 -88
- data/bin/cli.rb +9 -4
- data/bin/commands/assess_distribution.rb +34 -15
- data/bin/commands/benchmark.rb +65 -63
- data/bin/commands/test.rb +74 -96
- 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 +16 -23
- 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 +28 -3
- data/lib/featurevisor/hooks.rb +0 -159
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 202758e9fe9a2c81f67c3ea7456be5d068c1d9aa8ccf35f721a4ca447a94196a
|
|
4
|
+
data.tar.gz: bf2cae186ce5b6d3b483f4eab14768553cc5d2e09148c16ebbaf3ac60af7d7a0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: aa565646ae2e3b54d018044a79e92d9172e0ac8478dbb2dbee1699605a9457fbea6b694dc42c9027b40d4d2a3358c3146be946cfe307319119ab6ba2d03657ed
|
|
7
|
+
data.tar.gz: e8e689efcc96ac4ca3bd0ec7469895fbb1c9b4e70737d0f56f87fa9f0733395c0e0de6938a7b6c7c4c6bd7daef9f9818daa9163e508b0242836b750aa83e82f4
|
data/README.md
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
# Featurevisor Ruby SDK <!-- omit in toc -->
|
|
2
2
|
|
|
3
|
-
This is a port of Featurevisor [JavaScript SDK](https://featurevisor.com/docs/sdks/javascript/)
|
|
3
|
+
This is a port of Featurevisor [JavaScript SDK](https://featurevisor.com/docs/sdks/javascript/) v3.x to Ruby, providing a way to evaluate feature flags, variations, and variables in your Ruby applications.
|
|
4
4
|
|
|
5
|
-
This SDK is compatible with
|
|
5
|
+
This SDK is compatible with Featurevisor v3 projects and v2 datafiles.
|
|
6
6
|
|
|
7
7
|
## Table of contents <!-- omit in toc -->
|
|
8
8
|
|
|
9
9
|
- [Installation](#installation)
|
|
10
|
+
- [Public API](#public-api)
|
|
10
11
|
- [Initialization](#initialization)
|
|
11
12
|
- [Evaluation types](#evaluation-types)
|
|
12
13
|
- [Context](#context)
|
|
@@ -23,22 +24,26 @@ This SDK is compatible with [Featurevisor](https://featurevisor.com/) v2.0 proje
|
|
|
23
24
|
- [Initialize with sticky](#initialize-with-sticky)
|
|
24
25
|
- [Set sticky afterwards](#set-sticky-afterwards)
|
|
25
26
|
- [Setting datafile](#setting-datafile)
|
|
27
|
+
- [Merging by default](#merging-by-default)
|
|
28
|
+
- [Replacing](#replacing)
|
|
29
|
+
- [Loading datafiles on demand](#loading-datafiles-on-demand)
|
|
26
30
|
- [Updating datafile](#updating-datafile)
|
|
27
31
|
- [Interval-based update](#interval-based-update)
|
|
28
|
-
- [
|
|
32
|
+
- [Evaluation details](#evaluation-details)
|
|
33
|
+
- [Diagnostics](#diagnostics)
|
|
29
34
|
- [Levels](#levels)
|
|
30
|
-
- [Customizing levels](#customizing-levels)
|
|
31
35
|
- [Handler](#handler)
|
|
32
36
|
- [Events](#events)
|
|
33
37
|
- [`datafile_set`](#datafile_set)
|
|
34
38
|
- [`context_set`](#context_set)
|
|
35
39
|
- [`sticky_set`](#sticky_set)
|
|
36
|
-
- [
|
|
37
|
-
- [
|
|
38
|
-
- [Defining a
|
|
39
|
-
- [Registering
|
|
40
|
+
- [`error`](#error)
|
|
41
|
+
- [Modules](#modules)
|
|
42
|
+
- [Defining a module](#defining-a-module)
|
|
43
|
+
- [Registering modules](#registering-modules)
|
|
40
44
|
- [Child instance](#child-instance)
|
|
41
45
|
- [Close](#close)
|
|
46
|
+
- [OpenFeature](#openfeature)
|
|
42
47
|
- [CLI usage](#cli-usage)
|
|
43
48
|
- [Test](#test)
|
|
44
49
|
- [Test against local monorepo's example-1](#test-against-local-monorepos-example-1)
|
|
@@ -72,6 +77,18 @@ Or install it yourself as:
|
|
|
72
77
|
$ gem install featurevisor
|
|
73
78
|
```
|
|
74
79
|
|
|
80
|
+
## Public API
|
|
81
|
+
|
|
82
|
+
The main runtime API is `Featurevisor.create_featurevisor`:
|
|
83
|
+
|
|
84
|
+
```ruby
|
|
85
|
+
f = Featurevisor.create_featurevisor(
|
|
86
|
+
datafile: datafile_content
|
|
87
|
+
)
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
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
|
+
|
|
75
92
|
## Initialization
|
|
76
93
|
|
|
77
94
|
The SDK can be initialized by passing [datafile](https://featurevisor.com/docs/building-datafiles/) content directly:
|
|
@@ -89,7 +106,7 @@ response = Net::HTTP.get_response(URI(datafile_url))
|
|
|
89
106
|
datafile_content = JSON.parse(response.body, symbolize_names: true)
|
|
90
107
|
|
|
91
108
|
# Create SDK instance
|
|
92
|
-
f = Featurevisor.
|
|
109
|
+
f = Featurevisor.create_featurevisor(
|
|
93
110
|
datafile: datafile_content
|
|
94
111
|
)
|
|
95
112
|
```
|
|
@@ -101,10 +118,10 @@ Alternatively, you can pass a JSON string directly and the SDK will parse it aut
|
|
|
101
118
|
```ruby
|
|
102
119
|
# Option 1: Parse JSON yourself (recommended)
|
|
103
120
|
datafile_content = JSON.parse(json_string, symbolize_names: true)
|
|
104
|
-
f = Featurevisor.
|
|
121
|
+
f = Featurevisor.create_featurevisor(datafile: datafile_content)
|
|
105
122
|
|
|
106
123
|
# Option 2: Pass JSON string directly (automatic parsing)
|
|
107
|
-
f = Featurevisor.
|
|
124
|
+
f = Featurevisor.create_featurevisor(datafile: json_string)
|
|
108
125
|
```
|
|
109
126
|
|
|
110
127
|
## Evaluation types
|
|
@@ -142,7 +159,7 @@ You can set context at the time of initialization:
|
|
|
142
159
|
```ruby
|
|
143
160
|
require 'featurevisor'
|
|
144
161
|
|
|
145
|
-
f = Featurevisor.
|
|
162
|
+
f = Featurevisor.create_featurevisor(
|
|
146
163
|
context: {
|
|
147
164
|
deviceId: '123',
|
|
148
165
|
country: 'nl'
|
|
@@ -274,6 +291,8 @@ f.get_variable_object(feature_key, variable_key, context = {})
|
|
|
274
291
|
f.get_variable_json(feature_key, variable_key, context = {})
|
|
275
292
|
```
|
|
276
293
|
|
|
294
|
+
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.
|
|
295
|
+
|
|
277
296
|
## Getting all evaluations
|
|
278
297
|
|
|
279
298
|
You can get evaluations of all features available in the SDK instance:
|
|
@@ -304,12 +323,14 @@ This is handy especially when you want to pass all evaluations from a backend ap
|
|
|
304
323
|
|
|
305
324
|
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/):
|
|
306
325
|
|
|
326
|
+
Sticky values belong to an SDK or child instance. Evaluation options do not accept sticky overrides; use `spawn(context, sticky: ...)` when a child needs its own sticky state.
|
|
327
|
+
|
|
307
328
|
### Initialize with sticky
|
|
308
329
|
|
|
309
330
|
```ruby
|
|
310
331
|
require 'featurevisor'
|
|
311
332
|
|
|
312
|
-
f = Featurevisor.
|
|
333
|
+
f = Featurevisor.create_featurevisor(
|
|
313
334
|
sticky: {
|
|
314
335
|
myFeatureKey: {
|
|
315
336
|
enabled: true,
|
|
@@ -362,6 +383,49 @@ f.set_datafile(json_string)
|
|
|
362
383
|
|
|
363
384
|
**Important**: When calling `set_datafile()`, ensure JSON is parsed with `symbolize_names: true` if you're parsing it yourself.
|
|
364
385
|
|
|
386
|
+
### Merging by default
|
|
387
|
+
|
|
388
|
+
By default, `set_datafile(datafile)` merges the incoming datafile into the SDK's current datafile:
|
|
389
|
+
|
|
390
|
+
- top-level metadata such as `schemaVersion`, `revision`, and `featurevisorVersion` comes from the incoming datafile
|
|
391
|
+
- `segments` are merged, with incoming entries overriding existing ones
|
|
392
|
+
- `features` are merged, with incoming entries overriding existing ones
|
|
393
|
+
|
|
394
|
+
This means you can call `set_datafile` more than once with different datafiles, and the SDK instance accumulates their features and segments together.
|
|
395
|
+
|
|
396
|
+
### Replacing
|
|
397
|
+
|
|
398
|
+
To fully replace the stored datafile, pass `true` as the second argument:
|
|
399
|
+
|
|
400
|
+
```ruby
|
|
401
|
+
f.set_datafile(datafile_content, true)
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
### Loading datafiles on demand
|
|
405
|
+
|
|
406
|
+
Because merging is the default, a single SDK instance can start with a small datafile and load more datafiles later as your application needs them, instead of downloading every feature upfront.
|
|
407
|
+
|
|
408
|
+
This pairs well with [targets](https://featurevisor.com/docs/targets/), where each target produces a smaller datafile for a specific part of your application:
|
|
409
|
+
|
|
410
|
+
```ruby
|
|
411
|
+
require "open-uri"
|
|
412
|
+
|
|
413
|
+
f = Featurevisor.create_featurevisor({})
|
|
414
|
+
|
|
415
|
+
def load_datafile(f, target)
|
|
416
|
+
url = "https://cdn.yoursite.com/production/featurevisor-#{target}.json"
|
|
417
|
+
datafile = JSON.parse(URI.open(url).read, symbolize_names: true)
|
|
418
|
+
|
|
419
|
+
# merges into whatever was loaded before
|
|
420
|
+
f.set_datafile(datafile)
|
|
421
|
+
end
|
|
422
|
+
|
|
423
|
+
load_datafile(f, "products")
|
|
424
|
+
|
|
425
|
+
# later, when the user reaches checkout
|
|
426
|
+
load_datafile(f, "checkout")
|
|
427
|
+
```
|
|
428
|
+
|
|
365
429
|
### Updating datafile
|
|
366
430
|
|
|
367
431
|
You can set the datafile as many times as you want in your application, which will result in emitting a [`datafile_set`](#datafile_set) event that you can listen and react to accordingly.
|
|
@@ -400,65 +464,38 @@ end
|
|
|
400
464
|
Thread.new { update_datafile(f, datafile_url) }
|
|
401
465
|
```
|
|
402
466
|
|
|
403
|
-
##
|
|
467
|
+
## Diagnostics
|
|
404
468
|
|
|
405
|
-
By default, Featurevisor
|
|
469
|
+
By default, Featurevisor reports diagnostics to the console for `info` level and above with a `[Featurevisor]` prefix.
|
|
406
470
|
|
|
407
471
|
### Levels
|
|
408
472
|
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
- `error`
|
|
412
|
-
- `warn`
|
|
413
|
-
- `info`
|
|
414
|
-
- `debug`
|
|
415
|
-
|
|
416
|
-
### Customizing levels
|
|
473
|
+
Available diagnostic levels are `fatal`, `error`, `warn`, `info`, and `debug`.
|
|
417
474
|
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
Setting `debug` level will print out all logs, including `info`, `warn`, and `error` levels.
|
|
475
|
+
Set the level during initialization or update it afterwards:
|
|
421
476
|
|
|
422
477
|
```ruby
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
f = Featurevisor.create_instance(
|
|
426
|
-
logger: Featurevisor.create_logger(level: 'debug')
|
|
427
|
-
)
|
|
428
|
-
```
|
|
429
|
-
|
|
430
|
-
Alternatively, you can also set `log_level` directly:
|
|
431
|
-
|
|
432
|
-
```ruby
|
|
433
|
-
f = Featurevisor.create_instance(
|
|
434
|
-
log_level: 'debug'
|
|
435
|
-
)
|
|
436
|
-
```
|
|
437
|
-
|
|
438
|
-
You can also set log level from SDK instance afterwards:
|
|
439
|
-
|
|
440
|
-
```ruby
|
|
441
|
-
f.set_log_level('debug')
|
|
478
|
+
f = Featurevisor.create_featurevisor(log_level: "debug")
|
|
479
|
+
f.set_log_level("info")
|
|
442
480
|
```
|
|
443
481
|
|
|
444
482
|
### Handler
|
|
445
483
|
|
|
446
|
-
|
|
484
|
+
Use `on_diagnostic` to send structured diagnostics to your observability system:
|
|
447
485
|
|
|
448
486
|
```ruby
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
handler: ->(level, message, details) {
|
|
455
|
-
# do something with the log
|
|
456
|
-
}
|
|
457
|
-
)
|
|
487
|
+
f = Featurevisor.create_featurevisor(
|
|
488
|
+
log_level: "info",
|
|
489
|
+
on_diagnostic: ->(diagnostic) {
|
|
490
|
+
puts "#{diagnostic[:level]} #{diagnostic[:code]} #{diagnostic[:message]}"
|
|
491
|
+
}
|
|
458
492
|
)
|
|
459
493
|
```
|
|
460
494
|
|
|
461
|
-
|
|
495
|
+
Every diagnostic has `:level`, `:code`, `:message`, and an object-shaped `:details` hash. Optional `:module`, `:moduleName`, and `:originalError` fields describe provenance. Evaluation metadata belongs in `:details`.
|
|
496
|
+
|
|
497
|
+
Diagnostic handlers are isolated from SDK behavior. An exception in a handler does not stop other handlers or evaluations.
|
|
498
|
+
|
|
462
499
|
|
|
463
500
|
## Events
|
|
464
501
|
|
|
@@ -471,8 +508,8 @@ You can listen to these events that can occur at various stages in your applicat
|
|
|
471
508
|
```ruby
|
|
472
509
|
unsubscribe = f.on('datafile_set') do |event|
|
|
473
510
|
revision = event[:revision] # new revision
|
|
474
|
-
previous_revision = event[:
|
|
475
|
-
revision_changed = event[:
|
|
511
|
+
previous_revision = event[:previousRevision]
|
|
512
|
+
revision_changed = event[:revisionChanged] # true if revision has changed
|
|
476
513
|
|
|
477
514
|
# list of feature keys that have new updates,
|
|
478
515
|
# and you should re-evaluate them
|
|
@@ -493,6 +530,8 @@ The `features` array will contain keys of features that have either been:
|
|
|
493
530
|
|
|
494
531
|
compared to the previous datafile content that existed in the SDK instance.
|
|
495
532
|
|
|
533
|
+
The event also includes `replaced`, which is `true` when the datafile replaced the previous content instead of merging into it.
|
|
534
|
+
|
|
496
535
|
### `context_set`
|
|
497
536
|
|
|
498
537
|
```ruby
|
|
@@ -515,6 +554,20 @@ unsubscribe = f.on('sticky_set') do |event|
|
|
|
515
554
|
end
|
|
516
555
|
```
|
|
517
556
|
|
|
557
|
+
### `error`
|
|
558
|
+
|
|
559
|
+
```ruby
|
|
560
|
+
unsubscribe = f.on('error') do |event|
|
|
561
|
+
diagnostic = event[:diagnostic]
|
|
562
|
+
code = diagnostic[:code]
|
|
563
|
+
message = diagnostic[:message]
|
|
564
|
+
|
|
565
|
+
puts "Featurevisor error: #{code} #{message}"
|
|
566
|
+
end
|
|
567
|
+
```
|
|
568
|
+
|
|
569
|
+
The `error` event is emitted for diagnostics reported with `level: "error"`.
|
|
570
|
+
|
|
518
571
|
## Evaluation details
|
|
519
572
|
|
|
520
573
|
Besides logging with debug level enabled, you can also get more details about how the feature variations and variables are evaluated in the runtime against given context:
|
|
@@ -547,22 +600,33 @@ And optionally these properties depending on whether you are evaluating a featur
|
|
|
547
600
|
- `variable_value`: the variable value
|
|
548
601
|
- `variable_schema`: the variable schema
|
|
549
602
|
|
|
550
|
-
##
|
|
603
|
+
## Modules
|
|
551
604
|
|
|
552
|
-
|
|
605
|
+
Modules allow you to intercept the evaluation process and customize SDK behavior.
|
|
553
606
|
|
|
554
|
-
### Defining a
|
|
607
|
+
### Defining a module
|
|
555
608
|
|
|
556
|
-
A
|
|
609
|
+
A module is a simple hash with a unique recommended `name` and optional lifecycle functions:
|
|
610
|
+
|
|
611
|
+
If `setup` raises an exception, the module is not registered. Featurevisor removes subscriptions created during setup, reports `module_setup_error`, and calls `close` when present.
|
|
557
612
|
|
|
558
613
|
```ruby
|
|
559
614
|
require 'featurevisor'
|
|
560
615
|
|
|
561
|
-
|
|
562
|
-
#
|
|
563
|
-
name: 'my-custom-
|
|
616
|
+
my_custom_module = {
|
|
617
|
+
# recommended, and used for duplicate detection/removal
|
|
618
|
+
name: 'my-custom-module',
|
|
619
|
+
|
|
620
|
+
# rest of the properties below are all optional per module
|
|
564
621
|
|
|
565
|
-
#
|
|
622
|
+
# setup once, when the module is registered
|
|
623
|
+
setup: ->(api) {
|
|
624
|
+
revision = api[:get_revision].call
|
|
625
|
+
|
|
626
|
+
api[:on_diagnostic].call(->(diagnostic) {
|
|
627
|
+
puts diagnostic[:message]
|
|
628
|
+
})
|
|
629
|
+
},
|
|
566
630
|
|
|
567
631
|
# before evaluation
|
|
568
632
|
before: ->(options) {
|
|
@@ -592,26 +656,43 @@ my_custom_hook = {
|
|
|
592
656
|
bucket_value: ->(options) {
|
|
593
657
|
# return custom bucket value
|
|
594
658
|
options[:bucket_value]
|
|
659
|
+
},
|
|
660
|
+
|
|
661
|
+
# cleanup when module is removed or SDK is closed
|
|
662
|
+
close: -> {
|
|
663
|
+
# cleanup here
|
|
595
664
|
}
|
|
596
665
|
}
|
|
597
666
|
```
|
|
598
667
|
|
|
599
|
-
|
|
668
|
+
The module API passed to `setup` exposes:
|
|
600
669
|
|
|
601
|
-
|
|
670
|
+
- `get_revision`
|
|
671
|
+
- `on_diagnostic`
|
|
672
|
+
- `report_diagnostic`
|
|
673
|
+
|
|
674
|
+
### Registering modules
|
|
675
|
+
|
|
676
|
+
You can register modules at the time of SDK initialization:
|
|
602
677
|
|
|
603
678
|
```ruby
|
|
604
679
|
require 'featurevisor'
|
|
605
680
|
|
|
606
|
-
f = Featurevisor.
|
|
607
|
-
|
|
681
|
+
f = Featurevisor.create_featurevisor(
|
|
682
|
+
modules: [my_custom_module]
|
|
608
683
|
)
|
|
609
684
|
```
|
|
610
685
|
|
|
611
686
|
Or after initialization:
|
|
612
687
|
|
|
613
688
|
```ruby
|
|
614
|
-
f.
|
|
689
|
+
remove_module = f.add_module(my_custom_module)
|
|
690
|
+
|
|
691
|
+
# remove later by calling the returned function
|
|
692
|
+
remove_module.call
|
|
693
|
+
|
|
694
|
+
# or remove by name
|
|
695
|
+
f.remove_module('my-custom-module')
|
|
615
696
|
```
|
|
616
697
|
|
|
617
698
|
## Child instance
|
|
@@ -687,29 +768,24 @@ $ bundle exec featurevisor test \
|
|
|
687
768
|
--quiet|--verbose \
|
|
688
769
|
--onlyFailures \
|
|
689
770
|
--keyPattern="myFeatureKey" \
|
|
690
|
-
--assertionPattern="#1"
|
|
691
|
-
--with-scopes \
|
|
692
|
-
--with-tags
|
|
771
|
+
--assertionPattern="#1"
|
|
693
772
|
```
|
|
694
773
|
|
|
695
|
-
|
|
774
|
+
The Ruby test runner builds base datafiles and Target datafiles in memory via `npx featurevisor build --json`. When an assertion contains `target`, it is evaluated against the matching Target datafile.
|
|
696
775
|
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
For compatibility, camelCase aliases are also supported: `--withScopes` and `--withTags`.
|
|
776
|
+
All three commands accept repeatable `--target=<target>` options. `test` builds only the selected Target datafiles and runs untargeted assertions plus assertions for those targets. `benchmark` and `assess-distribution` run independently against every selected Target datafile. Without `--target`, existing project-wide behavior is preserved. Project definitions, test specs, Target discovery, and datafile generation continue to come from the Node.js CLI.
|
|
700
777
|
|
|
701
778
|
### Test against local monorepo's example-1
|
|
702
779
|
|
|
703
780
|
```bash
|
|
704
781
|
$ cd /absolute/path/to/featurevisor-ruby
|
|
705
|
-
$ bundle exec featurevisor test --projectDirectoryPath
|
|
706
|
-
$
|
|
707
|
-
$ bundle exec featurevisor test --projectDirectoryPath=./monorepo/examples/example-1 --with-tags
|
|
782
|
+
$ bundle exec ruby bin/featurevisor test --projectDirectoryPath=../featurevisor/examples/example-1 --onlyFailures
|
|
783
|
+
$ make test-example-1
|
|
708
784
|
```
|
|
709
785
|
|
|
710
786
|
### Benchmark
|
|
711
787
|
|
|
712
|
-
Learn more about benchmarking [here](https://featurevisor.com/docs/
|
|
788
|
+
Learn more about benchmarking [here](https://featurevisor.com/docs/cli/#benchmarking).
|
|
713
789
|
|
|
714
790
|
```bash
|
|
715
791
|
$ bundle exec featurevisor benchmark \
|
|
@@ -722,7 +798,7 @@ $ bundle exec featurevisor benchmark \
|
|
|
722
798
|
|
|
723
799
|
### Assess distribution
|
|
724
800
|
|
|
725
|
-
Learn more about assessing distribution [here](https://featurevisor.com/docs/
|
|
801
|
+
Learn more about assessing distribution [here](https://featurevisor.com/docs/cli/#assess-distribution).
|
|
726
802
|
|
|
727
803
|
```bash
|
|
728
804
|
$ bundle exec featurevisor assess-distribution \
|
|
@@ -736,30 +812,100 @@ $ bundle exec featurevisor assess-distribution \
|
|
|
736
812
|
--n=1000
|
|
737
813
|
```
|
|
738
814
|
|
|
815
|
+
## OpenFeature
|
|
816
|
+
|
|
817
|
+
The OpenFeature provider is published as a separate gem. This keeps OpenFeature code and dependencies out of applications that only use the Featurevisor SDK.
|
|
818
|
+
|
|
819
|
+
The provider currently requires Ruby 3.4 or newer because that is the minimum version supported by the official OpenFeature Ruby SDK.
|
|
820
|
+
|
|
821
|
+
Install the provider:
|
|
822
|
+
|
|
823
|
+
```ruby
|
|
824
|
+
gem "featurevisor-openfeature", "~> 1.1"
|
|
825
|
+
```
|
|
826
|
+
|
|
827
|
+
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.
|
|
828
|
+
|
|
829
|
+
```ruby
|
|
830
|
+
require "featurevisor/openfeature_provider"
|
|
831
|
+
|
|
832
|
+
provider = Featurevisor::OpenFeatureProvider.new(
|
|
833
|
+
datafile: datafile_content,
|
|
834
|
+
)
|
|
835
|
+
|
|
836
|
+
OpenFeature::SDK.configure do |config|
|
|
837
|
+
config.set_provider_and_wait(provider)
|
|
838
|
+
end
|
|
839
|
+
|
|
840
|
+
client = OpenFeature::SDK.build_client
|
|
841
|
+
enabled = client.fetch_boolean_value(
|
|
842
|
+
flag_key: "checkout",
|
|
843
|
+
default_value: false,
|
|
844
|
+
evaluation_context: OpenFeature::SDK::EvaluationContext.new(targeting_key: "user-123"),
|
|
845
|
+
)
|
|
846
|
+
```
|
|
847
|
+
|
|
848
|
+
Use `checkout` for a flag, `checkout:variation` for its variation, and `checkout:title` for its `title` variable. Boolean variables use the boolean resolver. Arrays, hashes, and JSON variables use the object resolver.
|
|
849
|
+
|
|
850
|
+
OpenFeature's targeting key maps to `userId` by default. `targeting_key_field`, `key_separator`, and `variation_key` can customize the mapping.
|
|
851
|
+
|
|
852
|
+
You can pass any Featurevisor initialization options directly to the provider. These options are used to create the Featurevisor instance owned by the provider:
|
|
853
|
+
|
|
854
|
+
```ruby
|
|
855
|
+
provider = Featurevisor::OpenFeatureProvider.new(
|
|
856
|
+
datafile: datafile_content,
|
|
857
|
+
targeting_key_field: "accountId",
|
|
858
|
+
key_separator: "/",
|
|
859
|
+
variation_key: "$variation",
|
|
860
|
+
)
|
|
861
|
+
```
|
|
862
|
+
|
|
863
|
+
You can also reuse an existing Featurevisor instance:
|
|
864
|
+
|
|
865
|
+
```ruby
|
|
866
|
+
featurevisor = Featurevisor.create_featurevisor(datafile: datafile_content)
|
|
867
|
+
provider = Featurevisor::OpenFeatureProvider.new(featurevisor: featurevisor)
|
|
868
|
+
```
|
|
869
|
+
|
|
870
|
+
The caller owns an instance passed this way. Provider shutdown does not close it. Call `featurevisor.close` when every consumer is finished with it. When the provider creates the instance from options, the provider owns and closes it. If both are supplied, `featurevisor` takes precedence over the options hash.
|
|
871
|
+
|
|
872
|
+
See the [OpenFeature provider guide](https://featurevisor.com/docs/sdks/openfeature/) for resolution reasons, errors, metadata, tracking, lifecycle, and providers for other languages.
|
|
873
|
+
|
|
739
874
|
<!-- FEATUREVISOR_DOCS_END -->
|
|
740
875
|
|
|
741
876
|
## Development
|
|
742
877
|
|
|
743
878
|
### Setting up
|
|
744
879
|
|
|
745
|
-
After checking out the
|
|
880
|
+
After checking out the repository, run `make install` to install dependencies.
|
|
746
881
|
|
|
747
882
|
### Running tests
|
|
748
883
|
|
|
749
884
|
```bash
|
|
750
885
|
$ bundle exec rspec
|
|
886
|
+
$ make test-base
|
|
887
|
+
$ make test-example-1
|
|
888
|
+
```
|
|
889
|
+
|
|
890
|
+
Build and verify both gems with:
|
|
891
|
+
|
|
892
|
+
```bash
|
|
893
|
+
$ make build
|
|
751
894
|
```
|
|
752
895
|
|
|
753
|
-
|
|
896
|
+
The build produces `featurevisor-VERSION.gem` and `featurevisor-openfeature-VERSION.gem`. The artifact verifier confirms that OpenFeature code and dependencies are absent from the base gem and present only in the provider gem.
|
|
754
897
|
|
|
755
898
|
### Releasing
|
|
756
899
|
|
|
757
|
-
- Update version in `lib/featurevisor/version.rb`
|
|
900
|
+
- Update the shared version in `lib/featurevisor/version.rb`
|
|
758
901
|
- Run `bundle install`
|
|
759
902
|
- Push commit to `main` branch
|
|
760
903
|
- Wait for CI to complete
|
|
761
|
-
- Tag the release with the version number
|
|
762
|
-
-
|
|
904
|
+
- Tag the release with the same version number, for example `v1.1.0`
|
|
905
|
+
- The workflow verifies that the tag matches the shared version
|
|
906
|
+
- The workflow publishes `featurevisor` first, followed by `featurevisor-openfeature`
|
|
907
|
+
|
|
908
|
+
The gems are separate RubyGems packages built from the same repository. Publishing the base gem first ensures the provider's exact Featurevisor dependency is available when the provider is published. If the second push fails, rerun or retry the provider publication without republishing the existing base version.
|
|
763
909
|
|
|
764
910
|
## License
|
|
765
911
|
|
data/bin/cli.rb
CHANGED
|
@@ -7,12 +7,13 @@ module FeaturevisorCLI
|
|
|
7
7
|
attr_accessor :command, :assertion_pattern, :context, :environment, :feature,
|
|
8
8
|
:key_pattern, :n, :only_failures, :quiet, :variable, :variation,
|
|
9
9
|
:verbose, :inflate, :show_datafile, :schema_version, :project_directory_path,
|
|
10
|
-
:populate_uuid, :with_scopes, :with_tags
|
|
10
|
+
:populate_uuid, :with_scopes, :with_tags, :targets
|
|
11
11
|
|
|
12
12
|
def initialize
|
|
13
13
|
@n = 1000
|
|
14
14
|
@project_directory_path = Dir.pwd
|
|
15
15
|
@populate_uuid = []
|
|
16
|
+
@targets = []
|
|
16
17
|
end
|
|
17
18
|
end
|
|
18
19
|
|
|
@@ -82,15 +83,15 @@ module FeaturevisorCLI
|
|
|
82
83
|
options.show_datafile = true
|
|
83
84
|
end
|
|
84
85
|
|
|
85
|
-
opts.on("--schemaVersion=VERSION", "
|
|
86
|
+
opts.on("--schemaVersion=VERSION", "--schema-version=VERSION", "Legacy schema version option accepted and ignored") do |v|
|
|
86
87
|
options.schema_version = v
|
|
87
88
|
end
|
|
88
89
|
|
|
89
|
-
opts.on("--with-scopes", "--withScopes", "
|
|
90
|
+
opts.on("--with-scopes", "--withScopes", "Legacy scope option accepted and ignored") do
|
|
90
91
|
options.with_scopes = true
|
|
91
92
|
end
|
|
92
93
|
|
|
93
|
-
opts.on("--with-tags", "--withTags", "
|
|
94
|
+
opts.on("--with-tags", "--withTags", "Legacy tag option accepted and ignored") do
|
|
94
95
|
options.with_tags = true
|
|
95
96
|
end
|
|
96
97
|
|
|
@@ -102,6 +103,10 @@ module FeaturevisorCLI
|
|
|
102
103
|
options.populate_uuid << v
|
|
103
104
|
end
|
|
104
105
|
|
|
106
|
+
opts.on("--target=TARGET", "Target datafile; repeat for multiple targets") do |v|
|
|
107
|
+
options.targets << v unless options.targets.include?(v)
|
|
108
|
+
end
|
|
109
|
+
|
|
105
110
|
opts.on("-h", "--help", "Show this help message") do
|
|
106
111
|
puts opts
|
|
107
112
|
exit
|