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
|
@@ -53,12 +53,12 @@ module Featurevisor
|
|
|
53
53
|
if value.is_a?(Array) && (context_value_from_path.is_a?(String) || context_value_from_path.is_a?(Numeric) || context_value_from_path.nil?)
|
|
54
54
|
# Check if the attribute key actually exists in the context
|
|
55
55
|
key_exists = context.key?(attribute.to_sym) || context.key?(attribute.to_s)
|
|
56
|
-
|
|
56
|
+
|
|
57
57
|
# If key doesn't exist, notIn should fail (return false), in should also fail
|
|
58
58
|
if !key_exists
|
|
59
59
|
return false
|
|
60
60
|
end
|
|
61
|
-
|
|
61
|
+
|
|
62
62
|
value_in_context = context_value_from_path.to_s
|
|
63
63
|
|
|
64
64
|
if operator == "in"
|
|
@@ -183,6 +183,8 @@ module Featurevisor
|
|
|
183
183
|
end
|
|
184
184
|
end
|
|
185
185
|
|
|
186
|
+
|
|
187
|
+
|
|
186
188
|
if conditions.is_a?(Array)
|
|
187
189
|
return conditions.all? { |c| all_conditions_are_matched(c, context) }
|
|
188
190
|
end
|
|
@@ -245,6 +247,8 @@ module Featurevisor
|
|
|
245
247
|
all_segments_are_matched({ "and" => group_segments["not"] }, context) == false
|
|
246
248
|
end
|
|
247
249
|
end
|
|
250
|
+
|
|
251
|
+
|
|
248
252
|
end
|
|
249
253
|
|
|
250
254
|
if group_segments.is_a?(Array)
|
|
@@ -17,7 +17,8 @@ module Featurevisor
|
|
|
17
17
|
VARIABLE_NOT_FOUND = "variable_not_found" # variable's schema is not defined in the feature
|
|
18
18
|
VARIABLE_DEFAULT = "variable_default" # default variable value used
|
|
19
19
|
VARIABLE_DISABLED = "variable_disabled" # feature is disabled, and variable's disabledValue is used
|
|
20
|
-
|
|
20
|
+
VARIABLE_OVERRIDE_VARIATION = "variable_override_variation" # variable overridden from inside a variation
|
|
21
|
+
VARIABLE_OVERRIDE_RULE = "variable_override_rule" # variable overridden from inside a rule
|
|
21
22
|
|
|
22
23
|
# Common
|
|
23
24
|
NO_MATCH = "no_match" # no rules matched
|
|
@@ -55,14 +56,14 @@ module Featurevisor
|
|
|
55
56
|
evaluation = evaluate(result_options)
|
|
56
57
|
|
|
57
58
|
# Default: variation
|
|
58
|
-
if options
|
|
59
|
+
if options.key?(:default_variation_value) &&
|
|
59
60
|
evaluation[:type] == "variation" &&
|
|
60
61
|
evaluation[:variation_value].nil?
|
|
61
62
|
evaluation[:variation_value] = options[:default_variation_value]
|
|
62
63
|
end
|
|
63
64
|
|
|
64
65
|
# Default: variable
|
|
65
|
-
if options
|
|
66
|
+
if options.key?(:default_variable_value) &&
|
|
66
67
|
evaluation[:type] == "variable" &&
|
|
67
68
|
evaluation[:variable_value].nil?
|
|
68
69
|
evaluation[:variable_value] = options[:default_variable_value]
|
|
@@ -132,10 +133,10 @@ module Featurevisor
|
|
|
132
133
|
if type == "variable"
|
|
133
134
|
if feature && variable_key &&
|
|
134
135
|
feature[:variablesSchema] &&
|
|
135
|
-
(feature[:variablesSchema]
|
|
136
|
-
variable_schema = feature[:variablesSchema]
|
|
136
|
+
has_key?(feature[:variablesSchema], variable_key)
|
|
137
|
+
variable_schema = fetch_with_symbol_key(feature[:variablesSchema], variable_key)
|
|
137
138
|
|
|
138
|
-
if variable_schema
|
|
139
|
+
if variable_schema.key?(:disabledValue)
|
|
139
140
|
# disabledValue: <value>
|
|
140
141
|
evaluation = {
|
|
141
142
|
type: type,
|
|
@@ -179,8 +180,8 @@ module Featurevisor
|
|
|
179
180
|
end
|
|
180
181
|
|
|
181
182
|
# Sticky
|
|
182
|
-
if sticky && (sticky
|
|
183
|
-
sticky_feature = sticky
|
|
183
|
+
if sticky && has_key?(sticky, feature_key)
|
|
184
|
+
sticky_feature = fetch_with_symbol_key(sticky, feature_key)
|
|
184
185
|
|
|
185
186
|
# flag
|
|
186
187
|
if type == "flag" && sticky_feature.key?(:enabled)
|
|
@@ -201,7 +202,7 @@ module Featurevisor
|
|
|
201
202
|
if type == "variation"
|
|
202
203
|
variation_value = sticky_feature[:variation]
|
|
203
204
|
|
|
204
|
-
|
|
205
|
+
unless variation_value.nil?
|
|
205
206
|
evaluation = {
|
|
206
207
|
type: type,
|
|
207
208
|
feature_key: feature_key,
|
|
@@ -219,8 +220,8 @@ module Featurevisor
|
|
|
219
220
|
if type == "variable" && variable_key
|
|
220
221
|
variables = sticky_feature[:variables]
|
|
221
222
|
|
|
222
|
-
if variables && (variables
|
|
223
|
-
variable_value = variables
|
|
223
|
+
if variables && has_key?(variables, variable_key)
|
|
224
|
+
variable_value = fetch_with_symbol_key(variables, variable_key)
|
|
224
225
|
evaluation = {
|
|
225
226
|
type: type,
|
|
226
227
|
feature_key: feature_key,
|
|
@@ -261,8 +262,8 @@ module Featurevisor
|
|
|
261
262
|
variable_schema = nil
|
|
262
263
|
|
|
263
264
|
if variable_key
|
|
264
|
-
if feature[:variablesSchema] && (feature[:variablesSchema]
|
|
265
|
-
variable_schema = feature[:variablesSchema]
|
|
265
|
+
if feature[:variablesSchema] && has_key?(feature[:variablesSchema], variable_key)
|
|
266
|
+
variable_schema = fetch_with_symbol_key(feature[:variablesSchema], variable_key)
|
|
266
267
|
end
|
|
267
268
|
|
|
268
269
|
# variable schema not found
|
|
@@ -343,8 +344,8 @@ module Featurevisor
|
|
|
343
344
|
end
|
|
344
345
|
|
|
345
346
|
# variable
|
|
346
|
-
if variable_key && force[:variables] && (force[:variables]
|
|
347
|
-
variable_value = force[:variables]
|
|
347
|
+
if variable_key && force[:variables] && has_key?(force[:variables], variable_key)
|
|
348
|
+
variable_value = fetch_with_symbol_key(force[:variables], variable_key)
|
|
348
349
|
evaluation = {
|
|
349
350
|
type: type,
|
|
350
351
|
feature_key: feature_key,
|
|
@@ -385,8 +386,8 @@ module Featurevisor
|
|
|
385
386
|
|
|
386
387
|
required_variation_value = nil
|
|
387
388
|
|
|
388
|
-
if required_variation_evaluation
|
|
389
|
-
required_variation_value = required_variation_evaluation
|
|
389
|
+
if has_key?(required_variation_evaluation, :variation_value)
|
|
390
|
+
required_variation_value = fetch_with_symbol_key(required_variation_evaluation, :variation_value)
|
|
390
391
|
elsif required_variation_evaluation[:variation]
|
|
391
392
|
required_variation_value = required_variation_evaluation[:variation][:value]
|
|
392
393
|
end
|
|
@@ -601,10 +602,50 @@ module Featurevisor
|
|
|
601
602
|
# variable
|
|
602
603
|
if type == "variable" && variable_key
|
|
603
604
|
# override from rule
|
|
605
|
+
if matched_traffic &&
|
|
606
|
+
matched_traffic[:variableOverrides] &&
|
|
607
|
+
has_key?(matched_traffic[:variableOverrides], variable_key)
|
|
608
|
+
overrides = fetch_with_symbol_key(matched_traffic[:variableOverrides], variable_key)
|
|
609
|
+
|
|
610
|
+
override_index = overrides.find_index do |o|
|
|
611
|
+
if o[:conditions]
|
|
612
|
+
conditions = o[:conditions].is_a?(String) && o[:conditions] != "*" ? JSON.parse(o[:conditions]) : o[:conditions]
|
|
613
|
+
datafile_reader.all_conditions_are_matched(conditions, context)
|
|
614
|
+
elsif o[:segments]
|
|
615
|
+
segments = datafile_reader.parse_segments_if_stringified(o[:segments])
|
|
616
|
+
datafile_reader.all_segments_are_matched(segments, context)
|
|
617
|
+
else
|
|
618
|
+
false
|
|
619
|
+
end
|
|
620
|
+
end
|
|
621
|
+
|
|
622
|
+
unless override_index.nil?
|
|
623
|
+
override = overrides[override_index]
|
|
624
|
+
|
|
625
|
+
evaluation = {
|
|
626
|
+
type: type,
|
|
627
|
+
feature_key: feature_key,
|
|
628
|
+
reason: Featurevisor::EvaluationReason::VARIABLE_OVERRIDE_RULE,
|
|
629
|
+
bucket_key: bucket_key,
|
|
630
|
+
bucket_value: bucket_value,
|
|
631
|
+
rule_key: matched_traffic[:key],
|
|
632
|
+
traffic: matched_traffic,
|
|
633
|
+
variable_key: variable_key,
|
|
634
|
+
variable_schema: variable_schema,
|
|
635
|
+
variable_value: override[:value],
|
|
636
|
+
variable_override_index: override_index
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
logger.debug("variable override from rule", evaluation)
|
|
640
|
+
|
|
641
|
+
return evaluation
|
|
642
|
+
end
|
|
643
|
+
end
|
|
644
|
+
|
|
604
645
|
if matched_traffic &&
|
|
605
646
|
matched_traffic[:variables] &&
|
|
606
|
-
(matched_traffic[:variables]
|
|
607
|
-
variable_value = matched_traffic[:variables]
|
|
647
|
+
has_key?(matched_traffic[:variables], variable_key)
|
|
648
|
+
variable_value = fetch_with_symbol_key(matched_traffic[:variables], variable_key)
|
|
608
649
|
evaluation = {
|
|
609
650
|
type: type,
|
|
610
651
|
feature_key: feature_key,
|
|
@@ -637,81 +678,38 @@ module Featurevisor
|
|
|
637
678
|
if variation_value && feature[:variations].is_a?(Array)
|
|
638
679
|
variation = feature[:variations].find { |v| v[:value] == variation_value }
|
|
639
680
|
|
|
640
|
-
if variation && variation[:variableOverrides] && (variation[:variableOverrides]
|
|
641
|
-
overrides = variation[:variableOverrides]
|
|
681
|
+
if variation && variation[:variableOverrides] && has_key?(variation[:variableOverrides], variable_key)
|
|
682
|
+
overrides = fetch_with_symbol_key(variation[:variableOverrides], variable_key)
|
|
642
683
|
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
context: context
|
|
648
|
-
})
|
|
649
|
-
|
|
650
|
-
override = overrides.find do |o|
|
|
651
|
-
logger.debug("evaluating override", {
|
|
652
|
-
feature_key: feature_key,
|
|
653
|
-
variable_key: variable_key,
|
|
654
|
-
override: o,
|
|
655
|
-
context: context
|
|
656
|
-
})
|
|
657
|
-
|
|
658
|
-
result = if o[:conditions]
|
|
659
|
-
matched = datafile_reader.all_conditions_are_matched(
|
|
660
|
-
o[:conditions].is_a?(String) && o[:conditions] != "*" ?
|
|
661
|
-
JSON.parse(o[:conditions]) : o[:conditions],
|
|
662
|
-
context
|
|
663
|
-
)
|
|
664
|
-
logger.debug("conditions match result", {
|
|
665
|
-
feature_key: feature_key,
|
|
666
|
-
variable_key: variable_key,
|
|
667
|
-
conditions: o[:conditions],
|
|
668
|
-
matched: matched
|
|
669
|
-
})
|
|
670
|
-
matched
|
|
684
|
+
override_index = overrides.find_index do |o|
|
|
685
|
+
if o[:conditions]
|
|
686
|
+
conditions = o[:conditions].is_a?(String) && o[:conditions] != "*" ? JSON.parse(o[:conditions]) : o[:conditions]
|
|
687
|
+
datafile_reader.all_conditions_are_matched(conditions, context)
|
|
671
688
|
elsif o[:segments]
|
|
672
689
|
segments = datafile_reader.parse_segments_if_stringified(o[:segments])
|
|
673
|
-
|
|
674
|
-
logger.debug("segments match result", {
|
|
675
|
-
feature_key: feature_key,
|
|
676
|
-
variable_key: variable_key,
|
|
677
|
-
segments: o[:segments],
|
|
678
|
-
parsed_segments: segments,
|
|
679
|
-
matched: matched
|
|
680
|
-
})
|
|
681
|
-
matched
|
|
690
|
+
datafile_reader.all_segments_are_matched(segments, context)
|
|
682
691
|
else
|
|
683
|
-
logger.debug("override has no conditions or segments", {
|
|
684
|
-
feature_key: feature_key,
|
|
685
|
-
variable_key: variable_key,
|
|
686
|
-
override: o
|
|
687
|
-
})
|
|
688
692
|
false
|
|
689
693
|
end
|
|
690
|
-
|
|
691
|
-
logger.debug("override evaluation result", {
|
|
692
|
-
feature_key: feature_key,
|
|
693
|
-
variable_key: variable_key,
|
|
694
|
-
result: result
|
|
695
|
-
})
|
|
696
|
-
|
|
697
|
-
result
|
|
698
694
|
end
|
|
699
695
|
|
|
700
|
-
|
|
696
|
+
unless override_index.nil?
|
|
697
|
+
override = overrides[override_index]
|
|
701
698
|
evaluation = {
|
|
702
699
|
type: type,
|
|
703
700
|
feature_key: feature_key,
|
|
704
|
-
reason: Featurevisor::EvaluationReason::
|
|
701
|
+
reason: Featurevisor::EvaluationReason::VARIABLE_OVERRIDE_VARIATION,
|
|
705
702
|
bucket_key: bucket_key,
|
|
706
703
|
bucket_value: bucket_value,
|
|
707
704
|
rule_key: matched_traffic&.[](:key),
|
|
708
705
|
traffic: matched_traffic,
|
|
709
706
|
variable_key: variable_key,
|
|
710
707
|
variable_schema: variable_schema,
|
|
711
|
-
variable_value: override[:value]
|
|
708
|
+
variable_value: override[:value],
|
|
709
|
+
variable_override_index: override_index
|
|
712
710
|
}
|
|
713
711
|
|
|
714
|
-
logger.debug("variable override", evaluation)
|
|
712
|
+
logger.debug("variable override from variation", evaluation)
|
|
715
713
|
|
|
716
714
|
return evaluation
|
|
717
715
|
end
|
|
@@ -719,8 +717,8 @@ module Featurevisor
|
|
|
719
717
|
|
|
720
718
|
if variation &&
|
|
721
719
|
variation[:variables] &&
|
|
722
|
-
(variation[:variables]
|
|
723
|
-
variable_value = variation[:variables]
|
|
720
|
+
has_key?(variation[:variables], variable_key)
|
|
721
|
+
variable_value = fetch_with_symbol_key(variation[:variables], variable_key)
|
|
724
722
|
evaluation = {
|
|
725
723
|
type: type,
|
|
726
724
|
feature_key: feature_key,
|
|
@@ -814,5 +812,20 @@ module Featurevisor
|
|
|
814
812
|
evaluation
|
|
815
813
|
end
|
|
816
814
|
end
|
|
815
|
+
|
|
816
|
+
def self.fetch_with_symbol_key(obj, key)
|
|
817
|
+
return obj[key] if obj.is_a?(Hash) && obj.key?(key)
|
|
818
|
+
|
|
819
|
+
symbol_key = key.to_sym
|
|
820
|
+
return obj[symbol_key] if obj.is_a?(Hash) && obj.key?(symbol_key)
|
|
821
|
+
|
|
822
|
+
nil
|
|
823
|
+
end
|
|
824
|
+
|
|
825
|
+
def self.has_key?(obj, key)
|
|
826
|
+
return false unless obj.is_a?(Hash)
|
|
827
|
+
|
|
828
|
+
obj.key?(key) || obj.key?(key.to_sym)
|
|
829
|
+
end
|
|
817
830
|
end
|
|
818
831
|
end
|
|
@@ -42,7 +42,7 @@ module Featurevisor
|
|
|
42
42
|
|
|
43
43
|
if options[:datafile]
|
|
44
44
|
@datafile_reader = Featurevisor::DatafileReader.new(
|
|
45
|
-
datafile: options[:datafile].is_a?(String) ? JSON.parse(options[:datafile]) : options[:datafile],
|
|
45
|
+
datafile: options[:datafile].is_a?(String) ? JSON.parse(options[:datafile], symbolize_names: true) : options[:datafile],
|
|
46
46
|
logger: @logger
|
|
47
47
|
)
|
|
48
48
|
end
|
|
@@ -61,7 +61,7 @@ module Featurevisor
|
|
|
61
61
|
def set_datafile(datafile)
|
|
62
62
|
begin
|
|
63
63
|
new_datafile_reader = Featurevisor::DatafileReader.new(
|
|
64
|
-
datafile: datafile.is_a?(String) ? JSON.parse(datafile) : datafile,
|
|
64
|
+
datafile: datafile.is_a?(String) ? JSON.parse(datafile, symbolize_names: true) : datafile,
|
|
65
65
|
logger: @logger
|
|
66
66
|
)
|
|
67
67
|
|
data/lib/featurevisor/version.rb
CHANGED