featurevisor 0.1.1 → 0.3.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 +49 -5
- data/bin/cli.rb +9 -1
- data/bin/commands/assess_distribution.rb +6 -20
- data/bin/commands/benchmark.rb +2 -16
- data/bin/commands/test.rb +335 -176
- data/lib/featurevisor/conditions.rb +2 -2
- data/lib/featurevisor/datafile_reader.rb +4 -0
- data/lib/featurevisor/evaluate.rb +90 -77
- data/lib/featurevisor/instance.rb +2 -2
- 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: 42c8ec3e5bf6293eeae189365b0383588a8cf7fdd5a5ee249a6fd44a2676403c
|
|
4
|
+
data.tar.gz: ce5794865b3dd5a057b9e0841814dab1c276b52237f230e23da6317f8dea3f70
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 41aefa056806a97bc1f5bf13d847007fd4b38fb176f97d1a09868434644fda2ed8cd58698ad9f5002a6ef5d30f7fc105accf7d505eb41fb71abe28a5bda6e47c
|
|
7
|
+
data.tar.gz: e4376d46699b8fe730d4dc4f73890fd43c6e879c446bc484dc6a47cc6b2c31f715af41b83b5f6189f9b8eeed3fd61b48213ec589855d909fe7ebccb702c04303
|
data/README.md
CHANGED
|
@@ -41,6 +41,7 @@ This SDK is compatible with [Featurevisor](https://featurevisor.com/) v2.0 proje
|
|
|
41
41
|
- [Close](#close)
|
|
42
42
|
- [CLI usage](#cli-usage)
|
|
43
43
|
- [Test](#test)
|
|
44
|
+
- [Test against local monorepo's example-1](#test-against-local-monorepos-example-1)
|
|
44
45
|
- [Benchmark](#benchmark)
|
|
45
46
|
- [Assess distribution](#assess-distribution)
|
|
46
47
|
- [Development](#development)
|
|
@@ -49,6 +50,8 @@ This SDK is compatible with [Featurevisor](https://featurevisor.com/) v2.0 proje
|
|
|
49
50
|
- [Releasing](#releasing)
|
|
50
51
|
- [License](#license)
|
|
51
52
|
|
|
53
|
+
<!-- FEATUREVISOR_DOCS_BEGIN -->
|
|
54
|
+
|
|
52
55
|
## Installation
|
|
53
56
|
|
|
54
57
|
Add this line to your application's Gemfile:
|
|
@@ -81,7 +84,9 @@ require 'json'
|
|
|
81
84
|
# Fetch datafile from URL
|
|
82
85
|
datafile_url = 'https://cdn.yoursite.com/datafile.json'
|
|
83
86
|
response = Net::HTTP.get_response(URI(datafile_url))
|
|
84
|
-
|
|
87
|
+
|
|
88
|
+
# Parse JSON with symbolized keys (required)
|
|
89
|
+
datafile_content = JSON.parse(response.body, symbolize_names: true)
|
|
85
90
|
|
|
86
91
|
# Create SDK instance
|
|
87
92
|
f = Featurevisor.create_instance(
|
|
@@ -89,6 +94,19 @@ f = Featurevisor.create_instance(
|
|
|
89
94
|
)
|
|
90
95
|
```
|
|
91
96
|
|
|
97
|
+
**Important**: When parsing JSON datafiles, you must use `symbolize_names: true` to ensure proper key handling by the SDK.
|
|
98
|
+
|
|
99
|
+
Alternatively, you can pass a JSON string directly and the SDK will parse it automatically:
|
|
100
|
+
|
|
101
|
+
```ruby
|
|
102
|
+
# Option 1: Parse JSON yourself (recommended)
|
|
103
|
+
datafile_content = JSON.parse(json_string, symbolize_names: true)
|
|
104
|
+
f = Featurevisor.create_instance(datafile: datafile_content)
|
|
105
|
+
|
|
106
|
+
# Option 2: Pass JSON string directly (automatic parsing)
|
|
107
|
+
f = Featurevisor.create_instance(datafile: json_string)
|
|
108
|
+
```
|
|
109
|
+
|
|
92
110
|
## Evaluation types
|
|
93
111
|
|
|
94
112
|
We can evaluate 3 types of values against a particular [feature](https://featurevisor.com/docs/features/):
|
|
@@ -334,9 +352,16 @@ f.set_sticky({
|
|
|
334
352
|
You may also initialize the SDK without passing `datafile`, and set it later on:
|
|
335
353
|
|
|
336
354
|
```ruby
|
|
355
|
+
# Parse with symbolized keys before setting
|
|
356
|
+
datafile_content = JSON.parse(json_string, symbolize_names: true)
|
|
337
357
|
f.set_datafile(datafile_content)
|
|
358
|
+
|
|
359
|
+
# Or pass JSON string directly for automatic parsing
|
|
360
|
+
f.set_datafile(json_string)
|
|
338
361
|
```
|
|
339
362
|
|
|
363
|
+
**Important**: When calling `set_datafile()`, ensure JSON is parsed with `symbolize_names: true` if you're parsing it yourself.
|
|
364
|
+
|
|
340
365
|
### Updating datafile
|
|
341
366
|
|
|
342
367
|
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.
|
|
@@ -359,7 +384,7 @@ require 'json'
|
|
|
359
384
|
def update_datafile(f, datafile_url)
|
|
360
385
|
loop do
|
|
361
386
|
sleep(5 * 60) # 5 minutes
|
|
362
|
-
|
|
387
|
+
|
|
363
388
|
begin
|
|
364
389
|
response = Net::HTTP.get_response(URI(datafile_url))
|
|
365
390
|
datafile_content = JSON.parse(response.body)
|
|
@@ -659,10 +684,27 @@ Additional options that are available:
|
|
|
659
684
|
```bash
|
|
660
685
|
$ bundle exec featurevisor test \
|
|
661
686
|
--projectDirectoryPath="/absolute/path/to/your/featurevisor/project" \
|
|
662
|
-
--quiet
|
|
687
|
+
--quiet|--verbose \
|
|
663
688
|
--onlyFailures \
|
|
664
689
|
--keyPattern="myFeatureKey" \
|
|
665
|
-
--assertionPattern="#1"
|
|
690
|
+
--assertionPattern="#1" \
|
|
691
|
+
--with-scopes \
|
|
692
|
+
--with-tags
|
|
693
|
+
```
|
|
694
|
+
|
|
695
|
+
`--with-scopes` and `--with-tags` make the Ruby test runner build scoped/tagged datafiles in memory (via `npx featurevisor build --json`) and evaluate matching assertions against those exact datafiles.
|
|
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).
|
|
698
|
+
|
|
699
|
+
For compatibility, camelCase aliases are also supported: `--withScopes` and `--withTags`.
|
|
700
|
+
|
|
701
|
+
### Test against local monorepo's example-1
|
|
702
|
+
|
|
703
|
+
```bash
|
|
704
|
+
$ cd /absolute/path/to/featurevisor-ruby
|
|
705
|
+
$ bundle exec featurevisor test --projectDirectoryPath=./monorepo/examples/example-1
|
|
706
|
+
$ bundle exec featurevisor test --projectDirectoryPath=./monorepo/examples/example-1 --with-scopes
|
|
707
|
+
$ bundle exec featurevisor test --projectDirectoryPath=./monorepo/examples/example-1 --with-tags
|
|
666
708
|
```
|
|
667
709
|
|
|
668
710
|
### Benchmark
|
|
@@ -694,6 +736,8 @@ $ bundle exec featurevisor assess-distribution \
|
|
|
694
736
|
--n=1000
|
|
695
737
|
```
|
|
696
738
|
|
|
739
|
+
<!-- FEATUREVISOR_DOCS_END -->
|
|
740
|
+
|
|
697
741
|
## Development
|
|
698
742
|
|
|
699
743
|
### Setting up
|
|
@@ -715,7 +759,7 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
|
|
|
715
759
|
- Push commit to `main` branch
|
|
716
760
|
- Wait for CI to complete
|
|
717
761
|
- Tag the release with the version number
|
|
718
|
-
- This will trigger a new release to RubyGems
|
|
762
|
+
- This will trigger a new release to [RubyGems](https://rubygems.org/gems/featurevisor)
|
|
719
763
|
|
|
720
764
|
## License
|
|
721
765
|
|
data/bin/cli.rb
CHANGED
|
@@ -7,7 +7,7 @@ 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
|
|
10
|
+
:populate_uuid, :with_scopes, :with_tags
|
|
11
11
|
|
|
12
12
|
def initialize
|
|
13
13
|
@n = 1000
|
|
@@ -86,6 +86,14 @@ module FeaturevisorCLI
|
|
|
86
86
|
options.schema_version = v
|
|
87
87
|
end
|
|
88
88
|
|
|
89
|
+
opts.on("--with-scopes", "--withScopes", "Test scoped assertions against scoped datafiles") do
|
|
90
|
+
options.with_scopes = true
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
opts.on("--with-tags", "--withTags", "Test tagged assertions against tagged datafiles") do
|
|
94
|
+
options.with_tags = true
|
|
95
|
+
end
|
|
96
|
+
|
|
89
97
|
opts.on("--projectDirectoryPath=PATH", "Project directory path") do |v|
|
|
90
98
|
options.project_directory_path = v
|
|
91
99
|
end
|
|
@@ -58,8 +58,8 @@ module FeaturevisorCLI
|
|
|
58
58
|
|
|
59
59
|
# Initialize evaluation counters
|
|
60
60
|
flag_evaluations = {
|
|
61
|
-
|
|
62
|
-
|
|
61
|
+
enabled: 0,
|
|
62
|
+
disabled: 0
|
|
63
63
|
}
|
|
64
64
|
variation_evaluations = {}
|
|
65
65
|
|
|
@@ -78,9 +78,9 @@ module FeaturevisorCLI
|
|
|
78
78
|
# Evaluate flag
|
|
79
79
|
flag_evaluation = instance.is_enabled(@options.feature, context_copy)
|
|
80
80
|
if flag_evaluation
|
|
81
|
-
flag_evaluations[
|
|
81
|
+
flag_evaluations[:enabled] += 1
|
|
82
82
|
else
|
|
83
|
-
flag_evaluations[
|
|
83
|
+
flag_evaluations[:disabled] += 1
|
|
84
84
|
end
|
|
85
85
|
|
|
86
86
|
# Evaluate variation if feature has variations
|
|
@@ -147,7 +147,7 @@ module FeaturevisorCLI
|
|
|
147
147
|
end
|
|
148
148
|
|
|
149
149
|
begin
|
|
150
|
-
JSON.parse(stdout)
|
|
150
|
+
JSON.parse(stdout, symbolize_names: true)
|
|
151
151
|
rescue JSON::ParserError => e
|
|
152
152
|
puts "Error: Failed to parse datafile JSON: #{e.message}"
|
|
153
153
|
exit 1
|
|
@@ -160,27 +160,13 @@ module FeaturevisorCLI
|
|
|
160
160
|
end
|
|
161
161
|
|
|
162
162
|
def create_instance(datafile)
|
|
163
|
-
# Convert datafile to proper format for the SDK
|
|
164
|
-
symbolized_datafile = symbolize_keys(datafile)
|
|
165
|
-
|
|
166
163
|
# Create SDK instance
|
|
167
164
|
Featurevisor.create_instance(
|
|
168
|
-
datafile:
|
|
165
|
+
datafile: datafile,
|
|
169
166
|
log_level: get_logger_level
|
|
170
167
|
)
|
|
171
168
|
end
|
|
172
169
|
|
|
173
|
-
def symbolize_keys(obj)
|
|
174
|
-
case obj
|
|
175
|
-
when Hash
|
|
176
|
-
obj.transform_keys(&:to_sym).transform_values { |v| symbolize_keys(v) }
|
|
177
|
-
when Array
|
|
178
|
-
obj.map { |item| symbolize_keys(item) }
|
|
179
|
-
else
|
|
180
|
-
obj
|
|
181
|
-
end
|
|
182
|
-
end
|
|
183
|
-
|
|
184
170
|
def get_logger_level
|
|
185
171
|
if @options.verbose
|
|
186
172
|
"debug"
|
data/bin/commands/benchmark.rb
CHANGED
|
@@ -120,7 +120,7 @@ module FeaturevisorCLI
|
|
|
120
120
|
|
|
121
121
|
# Parse the JSON output
|
|
122
122
|
begin
|
|
123
|
-
JSON.parse(datafile_output)
|
|
123
|
+
JSON.parse(datafile_output, symbolize_names: true)
|
|
124
124
|
rescue JSON::ParserError => e
|
|
125
125
|
puts "Error: Failed to parse datafile JSON: #{e.message}"
|
|
126
126
|
puts "Command output: #{datafile_output}"
|
|
@@ -143,16 +143,13 @@ module FeaturevisorCLI
|
|
|
143
143
|
end
|
|
144
144
|
|
|
145
145
|
def create_instance(datafile)
|
|
146
|
-
# Convert string keys to symbols for the SDK
|
|
147
|
-
symbolized_datafile = symbolize_keys(datafile)
|
|
148
|
-
|
|
149
146
|
# Create a real Featurevisor instance
|
|
150
147
|
instance = Featurevisor.create_instance(
|
|
151
148
|
log_level: get_logger_level
|
|
152
149
|
)
|
|
153
150
|
|
|
154
151
|
# Explicitly set the datafile
|
|
155
|
-
instance.set_datafile(
|
|
152
|
+
instance.set_datafile(datafile)
|
|
156
153
|
|
|
157
154
|
instance
|
|
158
155
|
end
|
|
@@ -167,17 +164,6 @@ module FeaturevisorCLI
|
|
|
167
164
|
end
|
|
168
165
|
end
|
|
169
166
|
|
|
170
|
-
def symbolize_keys(obj)
|
|
171
|
-
case obj
|
|
172
|
-
when Hash
|
|
173
|
-
obj.transform_keys(&:to_sym).transform_values { |v| symbolize_keys(v) }
|
|
174
|
-
when Array
|
|
175
|
-
obj.map { |item| symbolize_keys(item) }
|
|
176
|
-
else
|
|
177
|
-
obj
|
|
178
|
-
end
|
|
179
|
-
end
|
|
180
|
-
|
|
181
167
|
def benchmark_feature_flag(instance, feature_key, context, n)
|
|
182
168
|
start_time = Time.now
|
|
183
169
|
|