featurevisor 3.1.0 → 4.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 +300 -182
- data/bin/cli.rb +84 -17
- data/bin/commands/assess_distribution.rb +10 -52
- data/bin/commands/benchmark.rb +10 -48
- data/bin/commands/node_cli.rb +20 -0
- data/bin/commands/runtime_project.rb +63 -0
- data/bin/commands/test.rb +735 -260
- data/bin/commands.rb +1 -0
- data/bin/segment_tests.rb +89 -0
- data/bin/test_checks.rb +767 -0
- data/lib/featurevisor/child_instance.rb +15 -2
- data/lib/featurevisor/compare_versions.rb +18 -36
- data/lib/featurevisor/conditions.rb +91 -9
- data/lib/featurevisor/dependencies.rb +150 -0
- data/lib/featurevisor/emitter.rb +4 -0
- data/lib/featurevisor/evaluate.rb +64 -115
- data/lib/featurevisor/evaluation_data_provider.rb +100 -51
- data/lib/featurevisor/evaluation_session.rb +191 -0
- data/lib/featurevisor/events.rb +39 -100
- data/lib/featurevisor/instance.rb +258 -59
- data/lib/featurevisor/modules.rb +6 -46
- data/lib/featurevisor/mutations.rb +213 -0
- data/lib/featurevisor/normalize.rb +105 -0
- data/lib/featurevisor/unified_conditions.rb +79 -0
- data/lib/featurevisor/version.rb +1 -1
- data/lib/featurevisor.rb +7 -0
- metadata +10 -1
data/bin/commands.rb
CHANGED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module FeaturevisorCLI
|
|
4
|
+
module SegmentTests
|
|
5
|
+
module_function
|
|
6
|
+
|
|
7
|
+
def prepare(datafile, segments)
|
|
8
|
+
return datafile if segments.empty?
|
|
9
|
+
|
|
10
|
+
definitions = segments.transform_keys(&:to_sym).merge((datafile[:segments] || {}).transform_keys(&:to_sym))
|
|
11
|
+
features = (datafile[:features] || {}).dup
|
|
12
|
+
keys = {}
|
|
13
|
+
segments.each_key do |key|
|
|
14
|
+
helper = "__featurevisor_segment_test__#{key}"
|
|
15
|
+
helper += "_" while features.key?(helper.to_sym) || features.key?(helper)
|
|
16
|
+
keys[key.to_s] = helper
|
|
17
|
+
features[helper.to_sym] = { bucketBy: "__segment_test__", conditions: key.to_s,
|
|
18
|
+
traffic: [{ key: "segment", conditions: "*", percentage: 100_000 }] }
|
|
19
|
+
end
|
|
20
|
+
datafile.merge(features: features, segments: definitions, _segment_test_keys: keys)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def references!(key, datafile, segments)
|
|
24
|
+
pending = [[:segment, key, nil]]
|
|
25
|
+
seen = {}
|
|
26
|
+
expression = nil
|
|
27
|
+
expression = lambda do |value|
|
|
28
|
+
case value
|
|
29
|
+
when String
|
|
30
|
+
next if value == "*"
|
|
31
|
+
if value.lstrip.match?(/\A[\{\[\"]/)
|
|
32
|
+
begin
|
|
33
|
+
expression.call(JSON.parse(value, symbolize_names: true))
|
|
34
|
+
rescue JSON::ParserError
|
|
35
|
+
nil
|
|
36
|
+
end
|
|
37
|
+
else
|
|
38
|
+
pending << [:segment, value, nil]
|
|
39
|
+
end
|
|
40
|
+
when Array
|
|
41
|
+
value.each { |child| expression.call(child) }
|
|
42
|
+
when Hash
|
|
43
|
+
if value.key?(:variable)
|
|
44
|
+
pending << [value[:feature] ? :feature : :variable, value[:feature] || value[:variable], value[:feature] && value[:variable]]
|
|
45
|
+
elsif value.key?(:feature)
|
|
46
|
+
pending << [:feature, value[:feature], nil]
|
|
47
|
+
else
|
|
48
|
+
%i[and or not].each { |group| expression.call(value[group]) if value.key?(group) }
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
cursor = 0
|
|
53
|
+
while cursor < pending.length
|
|
54
|
+
kind, name, variable = pending[cursor]
|
|
55
|
+
cursor += 1
|
|
56
|
+
identity = [kind, name.to_s, variable]
|
|
57
|
+
next if seen[identity]
|
|
58
|
+
seen[identity] = true
|
|
59
|
+
definitions = datafile[{ feature: :features, segment: :segments, variable: :variables }.fetch(kind)] || {}
|
|
60
|
+
definition = kind == :segment ? TestChecks.keyed(segments, name) : nil
|
|
61
|
+
definition ||= TestChecks.keyed(definitions, name)
|
|
62
|
+
unless definition.is_a?(Hash)
|
|
63
|
+
raise TestChecks::CheckError, "#{kind} #{name.inspect} is absent from the selected datafile and test definitions"
|
|
64
|
+
end
|
|
65
|
+
if variable && !TestChecks.keyed(definition[:variablesSchema], variable)
|
|
66
|
+
raise TestChecks::CheckError, "variable #{variable.inspect} is absent from feature #{name.inspect} in the selected datafile"
|
|
67
|
+
end
|
|
68
|
+
normalized, = Featurevisor::Normalize.entity(kind, name, definition)
|
|
69
|
+
expression.call(normalized[:conditions])
|
|
70
|
+
if kind == :feature
|
|
71
|
+
(Array(normalized[:force]) + Array(normalized[:traffic])).each { |entry| expression.call(entry[:conditions]) }
|
|
72
|
+
if variable
|
|
73
|
+
(Array(normalized[:traffic]) + Array(normalized[:variations])).each do |entry|
|
|
74
|
+
Array(TestChecks.keyed(entry[:variableOverrides], variable)).each { |override| expression.call(override[:conditions]) }
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
elsif kind == :variable
|
|
78
|
+
(Array(normalized[:overrides]) + Array(normalized[:overrideProgram])).each { |entry| expression.call(entry[:conditions]) }
|
|
79
|
+
compose = normalized[:compose]
|
|
80
|
+
if compose.is_a?(String)
|
|
81
|
+
pending << [:variable, compose, nil]
|
|
82
|
+
elsif compose.is_a?(Array)
|
|
83
|
+
compose.each { |reference| expression.call(reference) }
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|