featurevisor 0.3.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +158 -83
- 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 +16 -2
- 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: 5951351cf7893e83e9f3994999f48389e1998bb8dec84092f3e7627f986bfd02
|
|
4
|
+
data.tar.gz: 3d24e02b8fd0708084b7716863396119728bcd83cbd0ac209b7a1c62fb982f68
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 368e45cb26cb6a98d13bc14b4fe9116bd36af47acadad3fbc99fc1926c8ce768edf5c9ade31aa36dd3c85ff95fc6dab2d3433d4d607bbeee99076a21838e0dae
|
|
7
|
+
data.tar.gz: 160b9eb960915113e3f9dfa3507b9f06664224ac3517d7a84432ec64d2d26d26c2ba379a09aeba64efe78a310972b5f497ad5d5ef1bbef1cc41ffa1408ffa70f
|
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,20 +24,23 @@ 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)
|
|
42
46
|
- [CLI usage](#cli-usage)
|
|
@@ -72,6 +76,18 @@ Or install it yourself as:
|
|
|
72
76
|
$ gem install featurevisor
|
|
73
77
|
```
|
|
74
78
|
|
|
79
|
+
## Public API
|
|
80
|
+
|
|
81
|
+
The main runtime API is `Featurevisor.create_featurevisor`:
|
|
82
|
+
|
|
83
|
+
```ruby
|
|
84
|
+
f = Featurevisor.create_featurevisor(
|
|
85
|
+
datafile: datafile_content
|
|
86
|
+
)
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
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.
|
|
90
|
+
|
|
75
91
|
## Initialization
|
|
76
92
|
|
|
77
93
|
The SDK can be initialized by passing [datafile](https://featurevisor.com/docs/building-datafiles/) content directly:
|
|
@@ -89,7 +105,7 @@ response = Net::HTTP.get_response(URI(datafile_url))
|
|
|
89
105
|
datafile_content = JSON.parse(response.body, symbolize_names: true)
|
|
90
106
|
|
|
91
107
|
# Create SDK instance
|
|
92
|
-
f = Featurevisor.
|
|
108
|
+
f = Featurevisor.create_featurevisor(
|
|
93
109
|
datafile: datafile_content
|
|
94
110
|
)
|
|
95
111
|
```
|
|
@@ -101,10 +117,10 @@ Alternatively, you can pass a JSON string directly and the SDK will parse it aut
|
|
|
101
117
|
```ruby
|
|
102
118
|
# Option 1: Parse JSON yourself (recommended)
|
|
103
119
|
datafile_content = JSON.parse(json_string, symbolize_names: true)
|
|
104
|
-
f = Featurevisor.
|
|
120
|
+
f = Featurevisor.create_featurevisor(datafile: datafile_content)
|
|
105
121
|
|
|
106
122
|
# Option 2: Pass JSON string directly (automatic parsing)
|
|
107
|
-
f = Featurevisor.
|
|
123
|
+
f = Featurevisor.create_featurevisor(datafile: json_string)
|
|
108
124
|
```
|
|
109
125
|
|
|
110
126
|
## Evaluation types
|
|
@@ -142,7 +158,7 @@ You can set context at the time of initialization:
|
|
|
142
158
|
```ruby
|
|
143
159
|
require 'featurevisor'
|
|
144
160
|
|
|
145
|
-
f = Featurevisor.
|
|
161
|
+
f = Featurevisor.create_featurevisor(
|
|
146
162
|
context: {
|
|
147
163
|
deviceId: '123',
|
|
148
164
|
country: 'nl'
|
|
@@ -274,6 +290,8 @@ f.get_variable_object(feature_key, variable_key, context = {})
|
|
|
274
290
|
f.get_variable_json(feature_key, variable_key, context = {})
|
|
275
291
|
```
|
|
276
292
|
|
|
293
|
+
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.
|
|
294
|
+
|
|
277
295
|
## Getting all evaluations
|
|
278
296
|
|
|
279
297
|
You can get evaluations of all features available in the SDK instance:
|
|
@@ -304,12 +322,14 @@ This is handy especially when you want to pass all evaluations from a backend ap
|
|
|
304
322
|
|
|
305
323
|
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
324
|
|
|
325
|
+
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.
|
|
326
|
+
|
|
307
327
|
### Initialize with sticky
|
|
308
328
|
|
|
309
329
|
```ruby
|
|
310
330
|
require 'featurevisor'
|
|
311
331
|
|
|
312
|
-
f = Featurevisor.
|
|
332
|
+
f = Featurevisor.create_featurevisor(
|
|
313
333
|
sticky: {
|
|
314
334
|
myFeatureKey: {
|
|
315
335
|
enabled: true,
|
|
@@ -362,6 +382,49 @@ f.set_datafile(json_string)
|
|
|
362
382
|
|
|
363
383
|
**Important**: When calling `set_datafile()`, ensure JSON is parsed with `symbolize_names: true` if you're parsing it yourself.
|
|
364
384
|
|
|
385
|
+
### Merging by default
|
|
386
|
+
|
|
387
|
+
By default, `set_datafile(datafile)` merges the incoming datafile into the SDK's current datafile:
|
|
388
|
+
|
|
389
|
+
- top-level metadata such as `schemaVersion`, `revision`, and `featurevisorVersion` comes from the incoming datafile
|
|
390
|
+
- `segments` are merged, with incoming entries overriding existing ones
|
|
391
|
+
- `features` are merged, with incoming entries overriding existing ones
|
|
392
|
+
|
|
393
|
+
This means you can call `set_datafile` more than once with different datafiles, and the SDK instance accumulates their features and segments together.
|
|
394
|
+
|
|
395
|
+
### Replacing
|
|
396
|
+
|
|
397
|
+
To fully replace the stored datafile, pass `true` as the second argument:
|
|
398
|
+
|
|
399
|
+
```ruby
|
|
400
|
+
f.set_datafile(datafile_content, true)
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
### Loading datafiles on demand
|
|
404
|
+
|
|
405
|
+
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.
|
|
406
|
+
|
|
407
|
+
This pairs well with [targets](https://featurevisor.com/docs/targets/), where each target produces a smaller datafile for a specific part of your application:
|
|
408
|
+
|
|
409
|
+
```ruby
|
|
410
|
+
require "open-uri"
|
|
411
|
+
|
|
412
|
+
f = Featurevisor.create_featurevisor({})
|
|
413
|
+
|
|
414
|
+
def load_datafile(f, target)
|
|
415
|
+
url = "https://cdn.yoursite.com/production/featurevisor-#{target}.json"
|
|
416
|
+
datafile = JSON.parse(URI.open(url).read, symbolize_names: true)
|
|
417
|
+
|
|
418
|
+
# merges into whatever was loaded before
|
|
419
|
+
f.set_datafile(datafile)
|
|
420
|
+
end
|
|
421
|
+
|
|
422
|
+
load_datafile(f, "products")
|
|
423
|
+
|
|
424
|
+
# later, when the user reaches checkout
|
|
425
|
+
load_datafile(f, "checkout")
|
|
426
|
+
```
|
|
427
|
+
|
|
365
428
|
### Updating datafile
|
|
366
429
|
|
|
367
430
|
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 +463,38 @@ end
|
|
|
400
463
|
Thread.new { update_datafile(f, datafile_url) }
|
|
401
464
|
```
|
|
402
465
|
|
|
403
|
-
##
|
|
466
|
+
## Diagnostics
|
|
404
467
|
|
|
405
|
-
By default, Featurevisor
|
|
468
|
+
By default, Featurevisor reports diagnostics to the console for `info` level and above with a `[Featurevisor]` prefix.
|
|
406
469
|
|
|
407
470
|
### Levels
|
|
408
471
|
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
- `error`
|
|
412
|
-
- `warn`
|
|
413
|
-
- `info`
|
|
414
|
-
- `debug`
|
|
415
|
-
|
|
416
|
-
### Customizing levels
|
|
417
|
-
|
|
418
|
-
If you choose `debug` level to make the logs more verbose, you can set it at the time of SDK initialization.
|
|
419
|
-
|
|
420
|
-
Setting `debug` level will print out all logs, including `info`, `warn`, and `error` levels.
|
|
472
|
+
Available diagnostic levels are `fatal`, `error`, `warn`, `info`, and `debug`.
|
|
421
473
|
|
|
422
|
-
|
|
423
|
-
require 'featurevisor'
|
|
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:
|
|
474
|
+
Set the level during initialization or update it afterwards:
|
|
431
475
|
|
|
432
476
|
```ruby
|
|
433
|
-
f = Featurevisor.
|
|
434
|
-
|
|
435
|
-
)
|
|
436
|
-
```
|
|
437
|
-
|
|
438
|
-
You can also set log level from SDK instance afterwards:
|
|
439
|
-
|
|
440
|
-
```ruby
|
|
441
|
-
f.set_log_level('debug')
|
|
477
|
+
f = Featurevisor.create_featurevisor(log_level: "debug")
|
|
478
|
+
f.set_log_level("info")
|
|
442
479
|
```
|
|
443
480
|
|
|
444
481
|
### Handler
|
|
445
482
|
|
|
446
|
-
|
|
483
|
+
Use `on_diagnostic` to send structured diagnostics to your observability system:
|
|
447
484
|
|
|
448
485
|
```ruby
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
handler: ->(level, message, details) {
|
|
455
|
-
# do something with the log
|
|
456
|
-
}
|
|
457
|
-
)
|
|
486
|
+
f = Featurevisor.create_featurevisor(
|
|
487
|
+
log_level: "info",
|
|
488
|
+
on_diagnostic: ->(diagnostic) {
|
|
489
|
+
puts "#{diagnostic[:level]} #{diagnostic[:code]} #{diagnostic[:message]}"
|
|
490
|
+
}
|
|
458
491
|
)
|
|
459
492
|
```
|
|
460
493
|
|
|
461
|
-
|
|
494
|
+
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`.
|
|
495
|
+
|
|
496
|
+
Diagnostic handlers are isolated from SDK behavior. An exception in a handler does not stop other handlers or evaluations.
|
|
497
|
+
|
|
462
498
|
|
|
463
499
|
## Events
|
|
464
500
|
|
|
@@ -471,8 +507,8 @@ You can listen to these events that can occur at various stages in your applicat
|
|
|
471
507
|
```ruby
|
|
472
508
|
unsubscribe = f.on('datafile_set') do |event|
|
|
473
509
|
revision = event[:revision] # new revision
|
|
474
|
-
previous_revision = event[:
|
|
475
|
-
revision_changed = event[:
|
|
510
|
+
previous_revision = event[:previousRevision]
|
|
511
|
+
revision_changed = event[:revisionChanged] # true if revision has changed
|
|
476
512
|
|
|
477
513
|
# list of feature keys that have new updates,
|
|
478
514
|
# and you should re-evaluate them
|
|
@@ -493,6 +529,8 @@ The `features` array will contain keys of features that have either been:
|
|
|
493
529
|
|
|
494
530
|
compared to the previous datafile content that existed in the SDK instance.
|
|
495
531
|
|
|
532
|
+
The event also includes `replaced`, which is `true` when the datafile replaced the previous content instead of merging into it.
|
|
533
|
+
|
|
496
534
|
### `context_set`
|
|
497
535
|
|
|
498
536
|
```ruby
|
|
@@ -515,6 +553,20 @@ unsubscribe = f.on('sticky_set') do |event|
|
|
|
515
553
|
end
|
|
516
554
|
```
|
|
517
555
|
|
|
556
|
+
### `error`
|
|
557
|
+
|
|
558
|
+
```ruby
|
|
559
|
+
unsubscribe = f.on('error') do |event|
|
|
560
|
+
diagnostic = event[:diagnostic]
|
|
561
|
+
code = diagnostic[:code]
|
|
562
|
+
message = diagnostic[:message]
|
|
563
|
+
|
|
564
|
+
puts "Featurevisor error: #{code} #{message}"
|
|
565
|
+
end
|
|
566
|
+
```
|
|
567
|
+
|
|
568
|
+
The `error` event is emitted for diagnostics reported with `level: "error"`.
|
|
569
|
+
|
|
518
570
|
## Evaluation details
|
|
519
571
|
|
|
520
572
|
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 +599,33 @@ And optionally these properties depending on whether you are evaluating a featur
|
|
|
547
599
|
- `variable_value`: the variable value
|
|
548
600
|
- `variable_schema`: the variable schema
|
|
549
601
|
|
|
550
|
-
##
|
|
602
|
+
## Modules
|
|
603
|
+
|
|
604
|
+
Modules allow you to intercept the evaluation process and customize SDK behavior.
|
|
551
605
|
|
|
552
|
-
|
|
606
|
+
### Defining a module
|
|
553
607
|
|
|
554
|
-
|
|
608
|
+
A module is a simple hash with a unique recommended `name` and optional lifecycle functions:
|
|
555
609
|
|
|
556
|
-
|
|
610
|
+
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
611
|
|
|
558
612
|
```ruby
|
|
559
613
|
require 'featurevisor'
|
|
560
614
|
|
|
561
|
-
|
|
562
|
-
#
|
|
563
|
-
name: 'my-custom-
|
|
615
|
+
my_custom_module = {
|
|
616
|
+
# recommended, and used for duplicate detection/removal
|
|
617
|
+
name: 'my-custom-module',
|
|
618
|
+
|
|
619
|
+
# rest of the properties below are all optional per module
|
|
620
|
+
|
|
621
|
+
# setup once, when the module is registered
|
|
622
|
+
setup: ->(api) {
|
|
623
|
+
revision = api[:get_revision].call
|
|
564
624
|
|
|
565
|
-
|
|
625
|
+
api[:on_diagnostic].call(->(diagnostic) {
|
|
626
|
+
puts diagnostic[:message]
|
|
627
|
+
})
|
|
628
|
+
},
|
|
566
629
|
|
|
567
630
|
# before evaluation
|
|
568
631
|
before: ->(options) {
|
|
@@ -592,26 +655,43 @@ my_custom_hook = {
|
|
|
592
655
|
bucket_value: ->(options) {
|
|
593
656
|
# return custom bucket value
|
|
594
657
|
options[:bucket_value]
|
|
658
|
+
},
|
|
659
|
+
|
|
660
|
+
# cleanup when module is removed or SDK is closed
|
|
661
|
+
close: -> {
|
|
662
|
+
# cleanup here
|
|
595
663
|
}
|
|
596
664
|
}
|
|
597
665
|
```
|
|
598
666
|
|
|
599
|
-
|
|
667
|
+
The module API passed to `setup` exposes:
|
|
668
|
+
|
|
669
|
+
- `get_revision`
|
|
670
|
+
- `on_diagnostic`
|
|
671
|
+
- `report_diagnostic`
|
|
600
672
|
|
|
601
|
-
|
|
673
|
+
### Registering modules
|
|
674
|
+
|
|
675
|
+
You can register modules at the time of SDK initialization:
|
|
602
676
|
|
|
603
677
|
```ruby
|
|
604
678
|
require 'featurevisor'
|
|
605
679
|
|
|
606
|
-
f = Featurevisor.
|
|
607
|
-
|
|
680
|
+
f = Featurevisor.create_featurevisor(
|
|
681
|
+
modules: [my_custom_module]
|
|
608
682
|
)
|
|
609
683
|
```
|
|
610
684
|
|
|
611
685
|
Or after initialization:
|
|
612
686
|
|
|
613
687
|
```ruby
|
|
614
|
-
f.
|
|
688
|
+
remove_module = f.add_module(my_custom_module)
|
|
689
|
+
|
|
690
|
+
# remove later by calling the returned function
|
|
691
|
+
remove_module.call
|
|
692
|
+
|
|
693
|
+
# or remove by name
|
|
694
|
+
f.remove_module('my-custom-module')
|
|
615
695
|
```
|
|
616
696
|
|
|
617
697
|
## Child instance
|
|
@@ -687,29 +767,24 @@ $ bundle exec featurevisor test \
|
|
|
687
767
|
--quiet|--verbose \
|
|
688
768
|
--onlyFailures \
|
|
689
769
|
--keyPattern="myFeatureKey" \
|
|
690
|
-
--assertionPattern="#1"
|
|
691
|
-
--with-scopes \
|
|
692
|
-
--with-tags
|
|
770
|
+
--assertionPattern="#1"
|
|
693
771
|
```
|
|
694
772
|
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
If an assertion references `scope` and `--with-scopes` is not provided, the runner still evaluates the assertion by merging that scope's configured context into the assertion context (without building scoped datafiles).
|
|
773
|
+
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.
|
|
698
774
|
|
|
699
|
-
|
|
775
|
+
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
776
|
|
|
701
777
|
### Test against local monorepo's example-1
|
|
702
778
|
|
|
703
779
|
```bash
|
|
704
780
|
$ 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
|
|
781
|
+
$ bundle exec ruby bin/featurevisor test --projectDirectoryPath=/Users/fahad/Projects/featurevisor/featurevisor/examples/example-1 --onlyFailures
|
|
782
|
+
$ make test-example-1
|
|
708
783
|
```
|
|
709
784
|
|
|
710
785
|
### Benchmark
|
|
711
786
|
|
|
712
|
-
Learn more about benchmarking [here](https://featurevisor.com/docs/
|
|
787
|
+
Learn more about benchmarking [here](https://featurevisor.com/docs/cli/#benchmarking).
|
|
713
788
|
|
|
714
789
|
```bash
|
|
715
790
|
$ bundle exec featurevisor benchmark \
|
|
@@ -722,7 +797,7 @@ $ bundle exec featurevisor benchmark \
|
|
|
722
797
|
|
|
723
798
|
### Assess distribution
|
|
724
799
|
|
|
725
|
-
Learn more about assessing distribution [here](https://featurevisor.com/docs/
|
|
800
|
+
Learn more about assessing distribution [here](https://featurevisor.com/docs/cli/#assess-distribution).
|
|
726
801
|
|
|
727
802
|
```bash
|
|
728
803
|
$ bundle exec featurevisor assess-distribution \
|
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
|
|
@@ -29,13 +29,21 @@ module FeaturevisorCLI
|
|
|
29
29
|
exit 1
|
|
30
30
|
end
|
|
31
31
|
|
|
32
|
-
puts ""
|
|
33
|
-
puts "Assessing distribution for feature: \"#{@options.feature}\"..."
|
|
34
|
-
puts ""
|
|
35
|
-
|
|
36
|
-
# Parse context if provided
|
|
37
32
|
context = parse_context
|
|
38
33
|
|
|
34
|
+
targets = resolve_targets
|
|
35
|
+
(targets.empty? ? [nil] : targets).each { |target| run_for_target(context, target) }
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
def run_for_target(context, target)
|
|
41
|
+
puts "\nAssess Featurevisor distribution"
|
|
42
|
+
puts " Feature: #{@options.feature}"
|
|
43
|
+
puts " Environment: #{@options.environment}"
|
|
44
|
+
puts " Target: #{target}" if target
|
|
45
|
+
puts " Iterations: #{@options.n}"
|
|
46
|
+
|
|
39
47
|
# Print context information
|
|
40
48
|
if @options.context
|
|
41
49
|
puts "Against context: #{@options.context}"
|
|
@@ -47,10 +55,10 @@ module FeaturevisorCLI
|
|
|
47
55
|
puts ""
|
|
48
56
|
|
|
49
57
|
# Build datafile
|
|
50
|
-
datafile = build_datafile(@options.environment)
|
|
58
|
+
datafile = build_datafile(@options.environment, target)
|
|
51
59
|
|
|
52
60
|
# Create SDK instance
|
|
53
|
-
instance =
|
|
61
|
+
instance = create_featurevisor(datafile)
|
|
54
62
|
|
|
55
63
|
# Check if feature has variations
|
|
56
64
|
feature = instance.get_feature(@options.feature)
|
|
@@ -104,7 +112,21 @@ module FeaturevisorCLI
|
|
|
104
112
|
end
|
|
105
113
|
end
|
|
106
114
|
|
|
107
|
-
|
|
115
|
+
def resolve_targets
|
|
116
|
+
return [] if @options.targets.empty?
|
|
117
|
+
stdout, stderr, status = Open3.capture3("npx", "featurevisor", "list", "--targets", "--json", chdir: @project_path)
|
|
118
|
+
unless status.success?
|
|
119
|
+
puts stderr
|
|
120
|
+
exit 1
|
|
121
|
+
end
|
|
122
|
+
available = JSON.parse(stdout).map { |item| item.is_a?(Hash) ? (item["key"] || item["name"]) : item }
|
|
123
|
+
unknown = @options.targets.find { |target| !available.include?(target) }
|
|
124
|
+
if unknown
|
|
125
|
+
puts "Unknown target \"#{unknown}\". Available targets: #{available.empty? ? "none" : available.join(", ")}."
|
|
126
|
+
exit 1
|
|
127
|
+
end
|
|
128
|
+
@options.targets
|
|
129
|
+
end
|
|
108
130
|
|
|
109
131
|
def parse_context
|
|
110
132
|
if @options.context
|
|
@@ -121,15 +143,12 @@ module FeaturevisorCLI
|
|
|
121
143
|
end
|
|
122
144
|
end
|
|
123
145
|
|
|
124
|
-
def build_datafile(environment)
|
|
146
|
+
def build_datafile(environment, target = nil)
|
|
125
147
|
puts "Building datafile for environment: #{environment}..."
|
|
126
148
|
|
|
127
149
|
# Build the command similar to Go implementation
|
|
128
150
|
command_parts = ["cd", @project_path, "&&", "npx", "featurevisor", "build", "--environment=#{environment}", "--json"]
|
|
129
|
-
|
|
130
|
-
if @options.schema_version
|
|
131
|
-
command_parts << "--schemaVersion=#{@options.schema_version}"
|
|
132
|
-
end
|
|
151
|
+
command_parts << "--target=#{target}" if target
|
|
133
152
|
|
|
134
153
|
if @options.inflate
|
|
135
154
|
command_parts << "--inflate=#{@options.inflate}"
|
|
@@ -159,9 +178,9 @@ module FeaturevisorCLI
|
|
|
159
178
|
[stdout, stderr, exit_status.exitstatus]
|
|
160
179
|
end
|
|
161
180
|
|
|
162
|
-
def
|
|
181
|
+
def create_featurevisor(datafile)
|
|
163
182
|
# Create SDK instance
|
|
164
|
-
Featurevisor.
|
|
183
|
+
Featurevisor.create_featurevisor(
|
|
165
184
|
datafile: datafile,
|
|
166
185
|
log_level: get_logger_level
|
|
167
186
|
)
|