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
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "securerandom"
|
|
4
|
+
|
|
5
|
+
module Featurevisor
|
|
6
|
+
# Modules extend evaluation behavior and SDK lifecycle.
|
|
7
|
+
module Modules
|
|
8
|
+
class FeaturevisorModule
|
|
9
|
+
attr_reader :id, :name
|
|
10
|
+
|
|
11
|
+
def initialize(options = {})
|
|
12
|
+
@id = SecureRandom.uuid
|
|
13
|
+
@name = options[:name]
|
|
14
|
+
@setup = options[:setup]
|
|
15
|
+
@before = options[:before]
|
|
16
|
+
@bucket_key = options[:bucket_key]
|
|
17
|
+
@bucket_value = options[:bucket_value]
|
|
18
|
+
@after = options[:after]
|
|
19
|
+
@close = options[:close]
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def call_setup(api)
|
|
23
|
+
@setup.call(api) if @setup
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def call_before(options)
|
|
27
|
+
return options unless @before
|
|
28
|
+
|
|
29
|
+
@before.call(options)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def call_bucket_key(options)
|
|
33
|
+
return options[:bucket_key] unless @bucket_key
|
|
34
|
+
|
|
35
|
+
@bucket_key.call(options)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def call_bucket_value(options)
|
|
39
|
+
return options[:bucket_value] unless @bucket_value
|
|
40
|
+
|
|
41
|
+
@bucket_value.call(options)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def call_after(evaluation, options)
|
|
45
|
+
return evaluation unless @after
|
|
46
|
+
|
|
47
|
+
@after.call(evaluation, options)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def call_close
|
|
51
|
+
@close.call if @close
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
class ModulesManager
|
|
56
|
+
attr_reader :modules
|
|
57
|
+
|
|
58
|
+
def initialize(options = {})
|
|
59
|
+
@modules = []
|
|
60
|
+
@report_diagnostic = options[:report_diagnostic]
|
|
61
|
+
@module_api_factory = options[:module_api_factory]
|
|
62
|
+
@clear_module_diagnostic_subscriptions = options[:clear_module_diagnostic_subscriptions]
|
|
63
|
+
|
|
64
|
+
(options[:modules] || []).each do |mod|
|
|
65
|
+
add(mod)
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def add(mod)
|
|
70
|
+
mod = FeaturevisorModule.new(mod) if mod.is_a?(Hash)
|
|
71
|
+
return nil unless mod
|
|
72
|
+
|
|
73
|
+
if mod.name && !mod.name.to_s.empty? && @modules.any? { |existing| existing.name == mod.name }
|
|
74
|
+
report(
|
|
75
|
+
{
|
|
76
|
+
level: "error",
|
|
77
|
+
code: "duplicate_module",
|
|
78
|
+
message: "Duplicate module name",
|
|
79
|
+
module_name: mod.name
|
|
80
|
+
},
|
|
81
|
+
nil
|
|
82
|
+
)
|
|
83
|
+
return nil
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
begin
|
|
87
|
+
mod.call_setup(@module_api_factory.call(mod)) if @module_api_factory
|
|
88
|
+
rescue => e
|
|
89
|
+
@clear_module_diagnostic_subscriptions.call(mod) if @clear_module_diagnostic_subscriptions
|
|
90
|
+
report(
|
|
91
|
+
{
|
|
92
|
+
level: "error",
|
|
93
|
+
code: "module_setup_error",
|
|
94
|
+
message: "Module setup failed",
|
|
95
|
+
module_name: mod.name,
|
|
96
|
+
original_error: e
|
|
97
|
+
},
|
|
98
|
+
nil
|
|
99
|
+
)
|
|
100
|
+
close_module(mod)
|
|
101
|
+
return nil
|
|
102
|
+
end
|
|
103
|
+
@modules << mod
|
|
104
|
+
|
|
105
|
+
-> { remove(mod) }
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def remove(name_or_module)
|
|
109
|
+
removed = []
|
|
110
|
+
@modules = @modules.reject do |mod|
|
|
111
|
+
matches = name_or_module.equal?(mod) || mod.name == name_or_module
|
|
112
|
+
removed << mod if matches
|
|
113
|
+
matches
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
removed.each do |mod|
|
|
117
|
+
@clear_module_diagnostic_subscriptions.call(mod) if @clear_module_diagnostic_subscriptions
|
|
118
|
+
close_module(mod)
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def get_all
|
|
123
|
+
@modules
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def run_before_modules(options)
|
|
127
|
+
@modules.reduce(options) do |result, mod|
|
|
128
|
+
mod.call_before(result)
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def run_bucket_key_modules(options)
|
|
133
|
+
bucket_key = options[:bucket_key]
|
|
134
|
+
@modules.each do |mod|
|
|
135
|
+
bucket_key = mod.call_bucket_key(options.merge(bucket_key: bucket_key))
|
|
136
|
+
end
|
|
137
|
+
bucket_key
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def run_bucket_value_modules(options)
|
|
141
|
+
bucket_value = options[:bucket_value]
|
|
142
|
+
@modules.each do |mod|
|
|
143
|
+
bucket_value = mod.call_bucket_value(options.merge(bucket_value: bucket_value))
|
|
144
|
+
end
|
|
145
|
+
bucket_value
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def run_after_modules(evaluation, options)
|
|
149
|
+
@modules.reduce(evaluation) do |result, mod|
|
|
150
|
+
mod.call_after(result, options)
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def close_all
|
|
155
|
+
@modules.each do |mod|
|
|
156
|
+
@clear_module_diagnostic_subscriptions.call(mod) if @clear_module_diagnostic_subscriptions
|
|
157
|
+
close_module(mod)
|
|
158
|
+
end
|
|
159
|
+
@modules = []
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
private
|
|
163
|
+
|
|
164
|
+
def close_module(mod)
|
|
165
|
+
mod.call_close
|
|
166
|
+
rescue => e
|
|
167
|
+
report(
|
|
168
|
+
{
|
|
169
|
+
level: "error",
|
|
170
|
+
code: "module_close_error",
|
|
171
|
+
message: "Module close failed",
|
|
172
|
+
module_name: mod.name,
|
|
173
|
+
original_error: e
|
|
174
|
+
},
|
|
175
|
+
nil
|
|
176
|
+
)
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def report(diagnostic, mod)
|
|
180
|
+
@report_diagnostic.call(diagnostic, mod) if @report_diagnostic
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
end
|
data/lib/featurevisor/version.rb
CHANGED
data/lib/featurevisor.rb
CHANGED
|
@@ -6,7 +6,7 @@ require_relative "featurevisor/emitter"
|
|
|
6
6
|
require_relative "featurevisor/conditions"
|
|
7
7
|
require_relative "featurevisor/datafile_reader"
|
|
8
8
|
require_relative "featurevisor/bucketer"
|
|
9
|
-
require_relative "featurevisor/
|
|
9
|
+
require_relative "featurevisor/modules"
|
|
10
10
|
require_relative "featurevisor/evaluate"
|
|
11
11
|
require_relative "featurevisor/instance"
|
|
12
12
|
require_relative "featurevisor/child_instance"
|
|
@@ -14,4 +14,7 @@ require_relative "featurevisor/events"
|
|
|
14
14
|
|
|
15
15
|
module Featurevisor
|
|
16
16
|
class Error < StandardError; end
|
|
17
|
+
|
|
18
|
+
private_constant :DatafileReader
|
|
19
|
+
private_constant :Logger
|
|
17
20
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: featurevisor
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 1.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Fahad Heylaal
|
|
@@ -9,6 +9,20 @@ bindir: bin
|
|
|
9
9
|
cert_chain: []
|
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: benchmark
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '0'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '0'
|
|
12
26
|
- !ruby/object:Gem::Dependency
|
|
13
27
|
name: rspec
|
|
14
28
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -60,9 +74,9 @@ files:
|
|
|
60
74
|
- lib/featurevisor/emitter.rb
|
|
61
75
|
- lib/featurevisor/evaluate.rb
|
|
62
76
|
- lib/featurevisor/events.rb
|
|
63
|
-
- lib/featurevisor/hooks.rb
|
|
64
77
|
- lib/featurevisor/instance.rb
|
|
65
78
|
- lib/featurevisor/logger.rb
|
|
79
|
+
- lib/featurevisor/modules.rb
|
|
66
80
|
- lib/featurevisor/murmurhash.rb
|
|
67
81
|
- lib/featurevisor/version.rb
|
|
68
82
|
homepage: https://featurevisor.com
|
data/lib/featurevisor/hooks.rb
DELETED
|
@@ -1,159 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Featurevisor
|
|
4
|
-
# Hooks module for extending evaluation behavior
|
|
5
|
-
module Hooks
|
|
6
|
-
# Hook interface for extending evaluation behavior
|
|
7
|
-
class Hook
|
|
8
|
-
attr_reader :name
|
|
9
|
-
|
|
10
|
-
# Initialize a new hook
|
|
11
|
-
# @param options [Hash] Hook options
|
|
12
|
-
# @option options [String] :name Hook name
|
|
13
|
-
# @option options [Proc, nil] :before Before evaluation hook
|
|
14
|
-
# @option options [Proc, nil] :bucket_key Bucket key configuration hook
|
|
15
|
-
# @option options [Proc, nil] :bucket_value Bucket value configuration hook
|
|
16
|
-
# @option options [Proc, nil] :after After evaluation hook
|
|
17
|
-
def initialize(options)
|
|
18
|
-
@name = options[:name]
|
|
19
|
-
@before = options[:before]
|
|
20
|
-
@bucket_key = options[:bucket_key]
|
|
21
|
-
@bucket_value = options[:bucket_value]
|
|
22
|
-
@after = options[:after]
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
# Call the before hook if defined
|
|
26
|
-
# @param options [Hash] Evaluation options
|
|
27
|
-
# @return [Hash] Modified evaluation options
|
|
28
|
-
def call_before(options)
|
|
29
|
-
return options unless @before
|
|
30
|
-
|
|
31
|
-
@before.call(options)
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
# Call the bucket key hook if defined
|
|
35
|
-
# @param options [Hash] Bucket key options
|
|
36
|
-
# @return [String] Modified bucket key
|
|
37
|
-
def call_bucket_key(options)
|
|
38
|
-
return options[:bucket_key] unless @bucket_key
|
|
39
|
-
|
|
40
|
-
@bucket_key.call(options)
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
# Call the bucket value hook if defined
|
|
44
|
-
# @param options [Hash] Bucket value options
|
|
45
|
-
# @return [Integer] Modified bucket value
|
|
46
|
-
def call_bucket_value(options)
|
|
47
|
-
return options[:bucket_value] unless @bucket_value
|
|
48
|
-
|
|
49
|
-
@bucket_value.call(options)
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
# Call the after hook if defined
|
|
53
|
-
# @param evaluation [Hash] Evaluation result
|
|
54
|
-
# @param options [Hash] Evaluation options
|
|
55
|
-
# @return [Hash] Modified evaluation result
|
|
56
|
-
def call_after(evaluation, options)
|
|
57
|
-
return evaluation unless @after
|
|
58
|
-
|
|
59
|
-
@after.call(evaluation, options)
|
|
60
|
-
end
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
# HooksManager class for managing hooks
|
|
64
|
-
class HooksManager
|
|
65
|
-
attr_reader :hooks, :logger
|
|
66
|
-
|
|
67
|
-
# Initialize a new HooksManager
|
|
68
|
-
# @param options [Hash] Options hash containing hooks and logger
|
|
69
|
-
# @option options [Array<Hook>] :hooks Array of hooks
|
|
70
|
-
# @option options [Logger] :logger Logger instance
|
|
71
|
-
def initialize(options)
|
|
72
|
-
@logger = options[:logger]
|
|
73
|
-
@hooks = []
|
|
74
|
-
|
|
75
|
-
if options[:hooks]
|
|
76
|
-
options[:hooks].each do |hook|
|
|
77
|
-
add(hook)
|
|
78
|
-
end
|
|
79
|
-
end
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
# Add a hook to the manager
|
|
83
|
-
# @param hook [Hook] Hook to add
|
|
84
|
-
# @return [Proc, nil] Remove function or nil if hook already exists
|
|
85
|
-
def add(hook)
|
|
86
|
-
if @hooks.any? { |existing_hook| existing_hook.name == hook.name }
|
|
87
|
-
@logger.error("Hook with name \"#{hook.name}\" already exists.", {
|
|
88
|
-
name: hook.name,
|
|
89
|
-
hook: hook
|
|
90
|
-
})
|
|
91
|
-
|
|
92
|
-
return nil
|
|
93
|
-
end
|
|
94
|
-
|
|
95
|
-
@hooks << hook
|
|
96
|
-
|
|
97
|
-
# Return a remove function
|
|
98
|
-
-> { remove(hook.name) }
|
|
99
|
-
end
|
|
100
|
-
|
|
101
|
-
# Remove a hook by name
|
|
102
|
-
# @param name [String] Hook name to remove
|
|
103
|
-
def remove(name)
|
|
104
|
-
@hooks = @hooks.reject { |hook| hook.name == name }
|
|
105
|
-
end
|
|
106
|
-
|
|
107
|
-
# Get all hooks
|
|
108
|
-
# @return [Array<Hook>] Array of all hooks
|
|
109
|
-
def get_all
|
|
110
|
-
@hooks
|
|
111
|
-
end
|
|
112
|
-
|
|
113
|
-
# Run before hooks
|
|
114
|
-
# @param options [Hash] Evaluation options
|
|
115
|
-
# @return [Hash] Modified evaluation options
|
|
116
|
-
def run_before_hooks(options)
|
|
117
|
-
result = options
|
|
118
|
-
@hooks.each do |hook|
|
|
119
|
-
result = hook.call_before(result)
|
|
120
|
-
end
|
|
121
|
-
result
|
|
122
|
-
end
|
|
123
|
-
|
|
124
|
-
# Run bucket key hooks
|
|
125
|
-
# @param options [Hash] Bucket key options
|
|
126
|
-
# @return [String] Modified bucket key
|
|
127
|
-
def run_bucket_key_hooks(options)
|
|
128
|
-
bucket_key = options[:bucket_key]
|
|
129
|
-
@hooks.each do |hook|
|
|
130
|
-
bucket_key = hook.call_bucket_key(options.merge(bucket_key: bucket_key))
|
|
131
|
-
end
|
|
132
|
-
bucket_key
|
|
133
|
-
end
|
|
134
|
-
|
|
135
|
-
# Run bucket value hooks
|
|
136
|
-
# @param options [Hash] Bucket value options
|
|
137
|
-
# @return [Integer] Modified bucket value
|
|
138
|
-
def run_bucket_value_hooks(options)
|
|
139
|
-
bucket_value = options[:bucket_value]
|
|
140
|
-
@hooks.each do |hook|
|
|
141
|
-
bucket_value = hook.call_bucket_value(options.merge(bucket_value: bucket_value))
|
|
142
|
-
end
|
|
143
|
-
bucket_value
|
|
144
|
-
end
|
|
145
|
-
|
|
146
|
-
# Run after hooks
|
|
147
|
-
# @param evaluation [Hash] Evaluation result
|
|
148
|
-
# @param options [Hash] Evaluation options
|
|
149
|
-
# @return [Hash] Modified evaluation result
|
|
150
|
-
def run_after_hooks(evaluation, options)
|
|
151
|
-
result = evaluation
|
|
152
|
-
@hooks.each do |hook|
|
|
153
|
-
result = hook.call_after(result, options)
|
|
154
|
-
end
|
|
155
|
-
result
|
|
156
|
-
end
|
|
157
|
-
end
|
|
158
|
-
end
|
|
159
|
-
end
|