featurevisor 1.1.0 → 3.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.
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "json"
4
+
3
5
  module Featurevisor
4
6
  # Events module for generating event parameters
5
7
  module Events
@@ -8,7 +10,7 @@ module Featurevisor
8
10
  # @param new_sticky [Hash] New sticky features
9
11
  # @param replace [Boolean] Whether features were replaced
10
12
  # @return [Hash] Event parameters
11
- def self.get_params_for_sticky_set_event(previous_sticky = {}, new_sticky = {}, replace = false)
13
+ def self.get_params_for_sticky_features_set_event(previous_sticky = {}, new_sticky = {}, replace = false)
12
14
  keys_before = previous_sticky.keys
13
15
  keys_after = new_sticky.keys
14
16
 
@@ -20,9 +22,16 @@ module Featurevisor
20
22
  }
21
23
  end
22
24
 
25
+ def self.get_params_for_sticky_variables_set_event(previous_sticky = {}, new_sticky = {}, replace = false)
26
+ {
27
+ variables: (previous_sticky.keys + new_sticky.keys).uniq,
28
+ replaced: replace
29
+ }
30
+ end
31
+
23
32
  # Get parameters for datafile set event
24
- # @param previous_reader [DatafileReader] Previous datafile reader
25
- # @param new_reader [DatafileReader] New datafile reader
33
+ # @param previous_reader [InstanceEvaluationDataProvider] Previous datafile
34
+ # @param new_reader [InstanceEvaluationDataProvider] New datafile
26
35
  # @return [Hash] Event parameters
27
36
  def self.get_params_for_datafile_set_event(previous_reader, new_reader, replace = false)
28
37
  previous_revision = previous_reader.get_revision
@@ -30,6 +39,8 @@ module Featurevisor
30
39
 
31
40
  new_revision = new_reader.get_revision
32
41
  new_feature_keys = new_reader.get_feature_keys
42
+ previous_variable_keys = previous_reader.get_variable_keys
43
+ new_variable_keys = new_reader.get_variable_keys
33
44
 
34
45
  # results
35
46
  removed_features = []
@@ -48,7 +59,8 @@ module Featurevisor
48
59
  previous_feature = previous_reader.get_feature(previous_feature_key)
49
60
  new_feature = new_reader.get_feature(previous_feature_key)
50
61
 
51
- if previous_feature && new_feature && previous_feature[:hash] != new_feature[:hash]
62
+ if previous_feature && new_feature &&
63
+ (previous_feature[:hash].nil? || new_feature[:hash].nil? || previous_feature[:hash] != new_feature[:hash])
52
64
  # feature was changed in new datafile
53
65
  changed_features << previous_feature_key
54
66
  end
@@ -64,14 +76,118 @@ module Featurevisor
64
76
 
65
77
  # combine all affected feature keys
66
78
  all_affected_features = (removed_features + changed_features + added_features).uniq
79
+ all_affected_variables = (previous_variable_keys + new_variable_keys).uniq.select do |key|
80
+ previous = previous_reader.get_global_variable(key)
81
+ current = new_reader.get_global_variable(key)
82
+ previous.nil? || current.nil? || previous[:hash].nil? || current[:hash].nil? || previous[:hash] != current[:hash]
83
+ end
84
+
85
+
86
+ changed_segments = (previous_reader.get_segment_keys + new_reader.get_segment_keys).uniq.select do |key|
87
+ previous_reader.get_segment(key) != new_reader.get_segment(key)
88
+ end.map(&:to_s)
89
+ feature_keys = (previous_feature_keys + new_feature_keys).uniq
90
+ loop do
91
+ before = all_affected_features.length
92
+ feature_keys.each do |key|
93
+ next if all_affected_features.include?(key)
94
+
95
+ candidates = [previous_reader.get_feature(key), new_reader.get_feature(key)].compact
96
+ all_affected_features << key if candidates.any? do |feature|
97
+ dependency = feature_dependencies(feature)
98
+ !(dependency[:segments].map(&:to_s) & changed_segments).empty? ||
99
+ !(dependency[:features].map(&:to_s) & all_affected_features.map(&:to_s)).empty?
100
+ end
101
+ end
102
+ break if before == all_affected_features.length
103
+ end
104
+
105
+ (previous_variable_keys + new_variable_keys).uniq.each do |key|
106
+ next if all_affected_variables.include?(key)
107
+
108
+ candidates = [previous_reader.get_global_variable(key), new_reader.get_global_variable(key)].compact
109
+ all_affected_variables << key if candidates.any? do |variable|
110
+ dependency = global_variable_dependencies(variable)
111
+ !(dependency[:segments].map(&:to_s) & changed_segments).empty? ||
112
+ !(dependency[:features].map(&:to_s) & all_affected_features.map(&:to_s)).empty?
113
+ end
114
+ end
67
115
 
68
116
  {
69
117
  revision: new_revision,
70
118
  previousRevision: previous_revision,
71
119
  revisionChanged: previous_revision != new_revision,
72
120
  features: all_affected_features,
121
+ variables: all_affected_variables,
73
122
  replaced: replace
74
123
  }
75
124
  end
125
+
126
+ def self.required_feature_keys(values)
127
+ items = values.is_a?(Array) ? values : [values].compact
128
+ items.filter_map do |required|
129
+ required.is_a?(String) ? required : (required[:feature] || required[:key])
130
+ end
131
+ end
132
+ private_class_method :required_feature_keys
133
+
134
+ def self.segment_keys(value)
135
+ return [] if value.nil? || value == "*"
136
+ if value.is_a?(String)
137
+ if value.start_with?("{", "[")
138
+ begin
139
+ return segment_keys(JSON.parse(value, symbolize_names: true))
140
+ rescue JSON::ParserError
141
+ return []
142
+ end
143
+ end
144
+ return [value]
145
+ end
146
+ return value.flat_map { |item| segment_keys(item) }.uniq if value.is_a?(Array)
147
+ return [] unless value.is_a?(Hash)
148
+
149
+ %i[and or not].flat_map { |key| segment_keys(value[key] || value[key.to_s]) }.uniq
150
+ end
151
+ private_class_method :segment_keys
152
+
153
+ def self.override_dependencies(groups)
154
+ overrides = groups.is_a?(Hash) ? groups.values.flatten : []
155
+ {
156
+ segments: overrides.flat_map { |override| segment_keys(override[:segments]) }.uniq,
157
+ features: overrides.flat_map { |override| required_feature_keys(override[:requiredFeatures]) }.uniq
158
+ }
159
+ end
160
+ private_class_method :override_dependencies
161
+
162
+ def self.feature_dependencies(feature)
163
+ requirements = feature.key?(:requiredFeatures) ? feature[:requiredFeatures] : feature[:required]
164
+ segments = []
165
+ features = required_feature_keys(requirements)
166
+ Array(feature[:traffic]).each do |traffic|
167
+ segments.concat(segment_keys(traffic[:segments]))
168
+ nested = override_dependencies(traffic[:variableOverrides])
169
+ segments.concat(nested[:segments])
170
+ features.concat(nested[:features])
171
+ end
172
+ Array(feature[:force]).each { |force| segments.concat(segment_keys(force[:segments])) }
173
+ Array(feature[:variations]).each do |variation|
174
+ nested = override_dependencies(variation[:variableOverrides])
175
+ segments.concat(nested[:segments])
176
+ features.concat(nested[:features])
177
+ end
178
+ { segments: segments.uniq, features: features.uniq }
179
+ end
180
+ private_class_method :feature_dependencies
181
+
182
+ def self.global_variable_dependencies(variable)
183
+ segments = []
184
+ features = required_feature_keys(variable[:requiredFeatures])
185
+ Array(variable[:overrides]).each do |override|
186
+ segments.concat(segment_keys(override[:segments]))
187
+ features.concat(required_feature_keys(override[:requiredFeatures]))
188
+ end
189
+ { segments: segments.uniq, features: features.uniq }
190
+ end
191
+ private_class_method :global_variable_dependencies
76
192
  end
77
193
  end