featurevisor 2.0.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,351 +1,126 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Featurevisor
4
- # Child instance class for managing child contexts and sticky features
4
+ # Child instance with isolated context and sticky state.
5
5
  class ChildInstance
6
- # Initialize a new child instance
7
- # @param options [Hash] Child instance options
8
- # @option options [Instance] :parent Parent instance
9
- # @option options [Hash] :context Child context
10
- # @option options [Hash] :sticky Child sticky features
11
6
  def initialize(options)
12
7
  @parent = options[:parent]
13
8
  @context = options[:context] || {}
14
- @sticky = options[:sticky] || {}
9
+ @sticky_features = options[:sticky_features] || {}
10
+ @sticky_variables = options[:sticky_variables] || {}
15
11
  @emitter = Featurevisor::Emitter.new
16
12
  @parent_unsubscribers = []
17
13
  end
18
14
 
19
- # Subscribe to an event
20
- # @param event_name [String] Event name
21
- # @param callback [Proc] Callback function
22
- # @return [Proc] Unsubscribe function
23
15
  def on(event_name, callback = nil, &block)
24
16
  callback = block if block_given?
25
-
26
- if event_name == "context_set" || event_name == "sticky_set"
27
- @emitter.on(event_name, callback)
28
- else
29
- parent_unsubscribe = @parent.on(event_name, callback)
30
- active = true
31
- unsubscribe = nil
32
- unsubscribe = proc do
33
- next unless active
17
+ if %w[context_set sticky_features_set sticky_variables_set].include?(event_name)
18
+ return @emitter.on(event_name, callback)
19
+ end
20
+
21
+ parent_unsubscribe = @parent.on(event_name, callback)
22
+ active = true
23
+ unsubscribe = nil
24
+ unsubscribe = proc do
25
+ next unless active
34
26
 
35
- active = false
36
- parent_unsubscribe.call
37
- @parent_unsubscribers.delete(unsubscribe)
38
- end
39
- @parent_unsubscribers << unsubscribe
40
- unsubscribe
27
+ active = false
28
+ parent_unsubscribe.call
29
+ @parent_unsubscribers.delete(unsubscribe)
41
30
  end
31
+ @parent_unsubscribers << unsubscribe
32
+ unsubscribe
42
33
  end
43
34
 
44
- # Close the child instance
45
35
  def close
46
36
  @parent_unsubscribers.dup.each(&:call)
47
37
  @parent_unsubscribers.clear
48
38
  @emitter.clear_all
49
39
  end
50
40
 
51
- # Set context
52
- # @param context [Hash] Context to set
53
- # @param replace [Boolean] Whether to replace existing context
54
41
  def set_context(context, replace = false)
55
- if replace
56
- @context = context
57
- else
58
- @context = { **@context, **context }
59
- end
60
-
61
- @emitter.trigger("context_set", {
62
- context: @context,
63
- replaced: replace
64
- })
42
+ @context = replace ? context : { **@context, **context }
43
+ @emitter.trigger("context_set", context: @context, replaced: replace)
65
44
  end
66
45
 
67
- # Get context
68
- # @param context [Hash, nil] Additional context to merge
69
- # @return [Hash] Merged context
70
46
  def get_context(context = nil)
71
- @parent.get_context({
72
- **@context,
73
- **(context || {})
74
- })
47
+ @parent.get_context({ **@context, **(context || {}) })
75
48
  end
76
49
 
77
- # Set sticky features
78
- # @param sticky [Hash] Sticky features
79
- # @param replace [Boolean] Whether to replace existing sticky features
80
- def set_sticky(sticky, replace = false)
81
- previous_sticky_features = @sticky || {}
82
-
83
- if replace
84
- @sticky = sticky
85
- else
86
- @sticky = {
87
- **@sticky,
88
- **sticky
89
- }
90
- end
50
+ def set_sticky_features(sticky, replace = false)
51
+ previous = @sticky_features
52
+ @sticky_features = replace ? sticky : { **@sticky_features, **sticky }
53
+ params = Featurevisor::Events.get_params_for_sticky_features_set_event(previous, @sticky_features, replace)
54
+ @emitter.trigger("sticky_features_set", params)
55
+ end
91
56
 
92
- params = Featurevisor::Events.get_params_for_sticky_set_event(previous_sticky_features, @sticky, replace)
93
- @emitter.trigger("sticky_set", params)
57
+ def set_sticky_variables(sticky, replace = false)
58
+ previous = @sticky_variables
59
+ @sticky_variables = replace ? sticky : { **@sticky_variables, **sticky }
60
+ @emitter.trigger(
61
+ "sticky_variables_set",
62
+ Featurevisor::Events.get_params_for_sticky_variables_set_event(previous, @sticky_variables, replace)
63
+ )
94
64
  end
95
65
 
96
- # Check if a feature is enabled
97
- # @param feature_key [String] Feature key
98
- # @param context [Hash] Context
99
- # @param options [Hash] Override options
100
- # @return [Boolean] True if feature is enabled
101
66
  def is_enabled(feature_key, context = {}, options = {})
102
- @parent.is_enabled(
103
- feature_key,
104
- {
105
- **@context,
106
- **context
107
- },
108
- {
109
- **options,
110
- __featurevisor_child_sticky: @sticky
111
- }
112
- )
67
+ @parent.is_enabled(feature_key, child_context(context), child_options(options))
113
68
  end
114
69
 
115
- # Evaluate a feature flag and return its full evaluation details.
116
70
  def evaluate_flag(feature_key, context = {}, options = {})
117
- @parent.evaluate_flag(
118
- feature_key,
119
- { **@context, **context },
120
- { **options, __featurevisor_child_sticky: @sticky }
121
- )
71
+ @parent.evaluate_flag(feature_key, child_context(context), child_options(options))
122
72
  end
123
73
 
124
- # Get variation value
125
- # @param feature_key [String] Feature key
126
- # @param context [Hash] Context
127
- # @param options [Hash] Override options
128
- # @return [String, nil] Variation value or nil
129
74
  def get_variation(feature_key, context = {}, options = {})
130
- @parent.get_variation(
131
- feature_key,
132
- {
133
- **@context,
134
- **context
135
- },
136
- {
137
- **options,
138
- __featurevisor_child_sticky: @sticky
139
- }
140
- )
75
+ @parent.get_variation(feature_key, child_context(context), child_options(options))
141
76
  end
142
77
 
143
- # Evaluate a variation and return its full evaluation details.
144
78
  def evaluate_variation(feature_key, context = {}, options = {})
145
- @parent.evaluate_variation(
146
- feature_key,
147
- { **@context, **context },
148
- { **options, __featurevisor_child_sticky: @sticky }
149
- )
150
- end
151
-
152
- # Get variable value
153
- # @param feature_key [String] Feature key
154
- # @param variable_key [String] Variable key
155
- # @param context [Hash] Context
156
- # @param options [Hash] Override options
157
- # @return [Object, nil] Variable value or nil
158
- def get_variable(feature_key, variable_key, context = {}, options = {})
159
- @parent.get_variable(
160
- feature_key,
161
- variable_key,
162
- {
163
- **@context,
164
- **context
165
- },
166
- {
167
- **options,
168
- __featurevisor_child_sticky: @sticky
169
- }
170
- )
79
+ @parent.evaluate_variation(feature_key, child_context(context), child_options(options))
171
80
  end
172
81
 
173
- # Evaluate a variable and return its full evaluation details.
174
- def evaluate_variable(feature_key, variable_key, context = {}, options = {})
175
- @parent.evaluate_variable(
176
- feature_key,
177
- variable_key,
178
- { **@context, **context },
179
- { **options, __featurevisor_child_sticky: @sticky }
180
- )
82
+ def get_variable(feature_or_variable_key, variable_key_or_context = nil, context_or_options = {}, options = {})
83
+ delegate_variable(:get_variable, feature_or_variable_key, variable_key_or_context, context_or_options, options)
181
84
  end
182
85
 
183
- # Get variable as boolean
184
- # @param feature_key [String] Feature key
185
- # @param variable_key [String] Variable key
186
- # @param context [Hash] Context
187
- # @param options [Hash] Override options
188
- # @return [Boolean, nil] Boolean value or nil
189
- def get_variable_boolean(feature_key, variable_key, context = {}, options = {})
190
- @parent.get_variable_boolean(
191
- feature_key,
192
- variable_key,
193
- {
194
- **@context,
195
- **context
196
- },
197
- {
198
- **options,
199
- __featurevisor_child_sticky: @sticky
200
- }
201
- )
86
+ def evaluate_variable(feature_or_variable_key, variable_key_or_context = nil, context_or_options = {}, options = {})
87
+ delegate_variable(:evaluate_variable, feature_or_variable_key, variable_key_or_context, context_or_options, options)
202
88
  end
203
89
 
204
- # Get variable as string
205
- # @param feature_key [String] Feature key
206
- # @param variable_key [String] Variable key
207
- # @param context [Hash] Context
208
- # @param options [Hash] Override options
209
- # @return [String, nil] String value or nil
210
- def get_variable_string(feature_key, variable_key, context = {}, options = {})
211
- @parent.get_variable_string(
212
- feature_key,
213
- variable_key,
214
- {
215
- **@context,
216
- **context
217
- },
218
- {
219
- **options,
220
- __featurevisor_child_sticky: @sticky
221
- }
222
- )
90
+ %i[boolean string integer double array object json].each do |type|
91
+ define_method("get_variable_#{type}") do |feature_or_variable_key, variable_key_or_context = nil, context_or_options = {}, options = {}|
92
+ delegate_variable("get_variable_#{type}".to_sym, feature_or_variable_key, variable_key_or_context, context_or_options, options)
93
+ end
223
94
  end
224
95
 
225
- # Get variable as integer
226
- # @param feature_key [String] Feature key
227
- # @param variable_key [String] Variable key
228
- # @param context [Hash] Context
229
- # @param options [Hash] Override options
230
- # @return [Integer, nil] Integer value or nil
231
- def get_variable_integer(feature_key, variable_key, context = {}, options = {})
232
- @parent.get_variable_integer(
233
- feature_key,
234
- variable_key,
235
- {
236
- **@context,
237
- **context
238
- },
239
- {
240
- **options,
241
- __featurevisor_child_sticky: @sticky
242
- }
243
- )
96
+ def get_feature_evaluations(context = {}, feature_keys = [], options = {})
97
+ @parent.get_feature_evaluations(child_context(context), feature_keys, child_options(options))
244
98
  end
245
99
 
246
- # Get variable as double
247
- # @param feature_key [String] Feature key
248
- # @param variable_key [String] Variable key
249
- # @param context [Hash] Context
250
- # @param options [Hash] Override options
251
- # @return [Float, nil] Float value or nil
252
- def get_variable_double(feature_key, variable_key, context = {}, options = {})
253
- @parent.get_variable_double(
254
- feature_key,
255
- variable_key,
256
- {
257
- **@context,
258
- **context
259
- },
260
- {
261
- **options,
262
- __featurevisor_child_sticky: @sticky
263
- }
264
- )
100
+ def get_variable_evaluations(context = {}, variable_keys = [], options = {})
101
+ @parent.get_variable_evaluations(child_context(context), variable_keys, child_options(options))
265
102
  end
266
103
 
267
- # Get variable as array
268
- # @param feature_key [String] Feature key
269
- # @param variable_key [String] Variable key
270
- # @param context [Hash] Context
271
- # @param options [Hash] Override options
272
- # @return [Array, nil] Array value or nil
273
- def get_variable_array(feature_key, variable_key, context = {}, options = {})
274
- @parent.get_variable_array(
275
- feature_key,
276
- variable_key,
277
- {
278
- **@context,
279
- **context
280
- },
281
- {
282
- **options,
283
- __featurevisor_child_sticky: @sticky
284
- }
285
- )
286
- end
104
+ private
287
105
 
288
- # Get variable as object
289
- # @param feature_key [String] Feature key
290
- # @param variable_key [String] Variable key
291
- # @param context [Hash] Context
292
- # @param options [Hash] Override options
293
- # @return [Hash, nil] Object value or nil
294
- def get_variable_object(feature_key, variable_key, context = {}, options = {})
295
- @parent.get_variable_object(
296
- feature_key,
297
- variable_key,
298
- {
299
- **@context,
300
- **context
301
- },
302
- {
303
- **options,
304
- __featurevisor_child_sticky: @sticky
305
- }
306
- )
106
+ def child_context(context)
107
+ { **@context, **context }
307
108
  end
308
109
 
309
- # Get variable as JSON
310
- # @param feature_key [String] Feature key
311
- # @param variable_key [String] Variable key
312
- # @param context [Hash] Context
313
- # @param options [Hash] Override options
314
- # @return [Object, nil] JSON value or nil
315
- def get_variable_json(feature_key, variable_key, context = {}, options = {})
316
- @parent.get_variable_json(
317
- feature_key,
318
- variable_key,
319
- {
320
- **@context,
321
- **context
322
- },
323
- {
324
- **options,
325
- __featurevisor_child_sticky: @sticky
326
- }
327
- )
110
+ def child_options(options)
111
+ {
112
+ **options,
113
+ __featurevisor_child_sticky_features: @sticky_features,
114
+ __featurevisor_child_sticky_variables: @sticky_variables
115
+ }
328
116
  end
329
117
 
330
- # Get all evaluations
331
- # @param context [Hash] Context
332
- # @param feature_keys [Array<String>] Feature keys to evaluate
333
- # @param options [Hash] Override options
334
- # @return [Hash] All evaluations
335
- def get_all_evaluations(context = {}, feature_keys = [], options = {})
336
- @parent.get_all_evaluations(
337
- {
338
- **@context,
339
- **context
340
- },
341
- feature_keys,
342
- {
343
- **options,
344
- __featurevisor_child_sticky: @sticky
345
- }
346
- )
118
+ def delegate_variable(method, first, second, third, fourth)
119
+ if second.nil? || second.is_a?(Hash)
120
+ @parent.public_send(method, first, child_context(second || {}), child_options(third))
121
+ else
122
+ @parent.public_send(method, first, second, child_context(third), child_options(fourth))
123
+ end
347
124
  end
348
-
349
- private
350
125
  end
351
126
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Featurevisor
4
4
  # Event names for the emitter
5
- EVENT_NAMES = %w[datafile_set context_set sticky_set error].freeze
5
+ EVENT_NAMES = %w[datafile_set context_set sticky_features_set sticky_variables_set error].freeze
6
6
 
7
7
  # Event emitter class for handling event subscriptions and triggers
8
8
  class Emitter
@@ -19,6 +19,7 @@ module Featurevisor
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
21
  VARIABLE_OVERRIDE_RULE = "variable_override_rule" # variable overridden from inside a rule
22
+ REQUIRED_FEATURES_UNMET = "required_features_unmet"
22
23
 
23
24
  # Common
24
25
  NO_MATCH = "no_match" # no rules matched
@@ -35,6 +36,50 @@ module Featurevisor
35
36
 
36
37
  # Evaluation module for feature flag evaluation
37
38
  module Evaluate
39
+ def self.required_features_are_matched(requirements, datafile, options)
40
+ return true if requirements.nil?
41
+
42
+ items = requirements.is_a?(Array) ? requirements : [requirements]
43
+ clean_options = options.reject do |key, _|
44
+ %i[feature_key variable_key default_variation_value default_variable_value].include?(key)
45
+ end
46
+ items.all? do |required|
47
+ if required.is_a?(String)
48
+ key = required
49
+ enabled = true
50
+ variation = nil
51
+ elsif required.key?(:feature)
52
+ key = required[:feature]
53
+ enabled = required.key?(:enabled) ? required[:enabled] : true
54
+ variation = required[:variation]
55
+ else
56
+ key = required[:key]
57
+ enabled = true
58
+ variation = required[:variation]
59
+ end
60
+
61
+ flag = evaluate_with_modules(clean_options.merge(type: "flag", feature_key: key, datafile: datafile))
62
+ next false unless (flag[:enabled] == true) == enabled
63
+ next true if variation.nil?
64
+
65
+ evaluated_variation = evaluate_with_modules(clean_options.merge(type: "variation", feature_key: key, datafile: datafile))
66
+ value = evaluated_variation.key?(:variation_value) ? evaluated_variation[:variation_value] : evaluated_variation.dig(:variation, :value)
67
+ value == variation
68
+ end
69
+ end
70
+
71
+ def self.variable_override_matches?(override, datafile, context, options)
72
+ return false unless required_features_are_matched(override[:requiredFeatures], datafile, options)
73
+
74
+ conditions_match = !override[:conditions] || datafile.all_conditions_are_matched(
75
+ datafile.parse_conditions_if_stringified(override[:conditions]), context
76
+ )
77
+ segments_match = !override[:segments] || datafile.all_segments_are_matched(
78
+ datafile.parse_segments_if_stringified(override[:segments]), context
79
+ )
80
+ conditions_match && segments_match &&
81
+ (override.key?(:conditions) || override.key?(:segments) || override.key?(:requiredFeatures))
82
+ end
38
83
 
39
84
  # Evaluate with modules
40
85
  # @param options [Hash] Evaluation options
@@ -53,18 +98,18 @@ module Featurevisor
53
98
  evaluation = evaluate(result_options)
54
99
 
55
100
  # Default: variation
56
- if options.key?(:default_variation_value) &&
101
+ if result_options.key?(:default_variation_value) &&
57
102
  evaluation[:type] == "variation" &&
58
103
  !evaluation.key?(:variation_value) &&
59
104
  !evaluation.key?(:variation)
60
- evaluation[:variation_value] = options[:default_variation_value]
105
+ evaluation[:variation_value] = result_options[:default_variation_value]
61
106
  end
62
107
 
63
108
  # Default: variable
64
- if options.key?(:default_variable_value) &&
109
+ if result_options.key?(:default_variable_value) &&
65
110
  evaluation[:type] == "variable" &&
66
111
  !evaluation.key?(:variable_value)
67
- evaluation[:variable_value] = options[:default_variable_value]
112
+ evaluation[:variable_value] = result_options[:default_variable_value]
68
113
  end
69
114
 
70
115
  # Run after modules
@@ -358,39 +403,9 @@ module Featurevisor
358
403
  end
359
404
 
360
405
  # Required
361
- if type == "flag" && feature[:required] && feature[:required].length > 0
362
- required_features_are_enabled = feature[:required].all? do |required|
363
- required_key = nil
364
- required_variation = nil
365
-
366
- if required.is_a?(String)
367
- required_key = required
368
- else
369
- required_key = required[:key]
370
- required_variation = required[:variation]
371
- end
372
-
373
- required_evaluation = evaluate(options.merge(type: "flag", feature_key: required_key))
374
- required_is_enabled = required_evaluation[:enabled]
375
-
376
- next false unless required_is_enabled
377
-
378
- if required_variation
379
- required_variation_evaluation = evaluate(options.merge(type: "variation", feature_key: required_key))
380
-
381
- required_variation_value = nil
382
-
383
- if has_key?(required_variation_evaluation, :variation_value)
384
- required_variation_value = fetch_with_symbol_key(required_variation_evaluation, :variation_value)
385
- elsif required_variation_evaluation[:variation]
386
- required_variation_value = required_variation_evaluation[:variation][:value]
387
- end
388
-
389
- next required_variation_value == required_variation
390
- end
391
-
392
- true
393
- end
406
+ required_features = feature[:requiredFeatures] || feature[:required]
407
+ if type == "flag" && required_features && !Array(required_features).empty?
408
+ required_features_are_enabled = required_features_are_matched(required_features, datafile, options)
394
409
 
395
410
  unless required_features_are_enabled
396
411
  evaluation = {
@@ -398,6 +413,7 @@ module Featurevisor
398
413
  feature_key: feature_key,
399
414
  reason: Featurevisor::EvaluationReason::REQUIRED,
400
415
  required: feature[:required],
416
+ required_features: feature[:requiredFeatures],
401
417
  enabled: required_features_are_enabled
402
418
  }
403
419
 
@@ -601,17 +617,7 @@ module Featurevisor
601
617
  has_key?(matched_traffic[:variableOverrides], variable_key)
602
618
  overrides = fetch_with_symbol_key(matched_traffic[:variableOverrides], variable_key)
603
619
 
604
- override_index = overrides.find_index do |o|
605
- if o[:conditions]
606
- conditions = o[:conditions].is_a?(String) && o[:conditions] != "*" ? JSON.parse(o[:conditions]) : o[:conditions]
607
- datafile.all_conditions_are_matched(conditions, context)
608
- elsif o[:segments]
609
- segments = datafile.parse_segments_if_stringified(o[:segments])
610
- datafile.all_segments_are_matched(segments, context)
611
- else
612
- false
613
- end
614
- end
620
+ override_index = overrides.find_index { |o| variable_override_matches?(o, datafile, context, options) }
615
621
 
616
622
  unless override_index.nil?
617
623
  override = overrides[override_index]
@@ -627,7 +633,8 @@ module Featurevisor
627
633
  variable_key: variable_key,
628
634
  variable_schema: variable_schema,
629
635
  variable_value: override[:value],
630
- variable_override_index: override_index
636
+ variable_override_index: override_index,
637
+ variable_override_key: override[:key]
631
638
  }
632
639
 
633
640
  diagnostics.debug("variable override from rule", evaluation)
@@ -675,17 +682,7 @@ module Featurevisor
675
682
  if variation && variation[:variableOverrides] && has_key?(variation[:variableOverrides], variable_key)
676
683
  overrides = fetch_with_symbol_key(variation[:variableOverrides], variable_key)
677
684
 
678
- override_index = overrides.find_index do |o|
679
- if o[:conditions]
680
- conditions = o[:conditions].is_a?(String) && o[:conditions] != "*" ? JSON.parse(o[:conditions]) : o[:conditions]
681
- datafile.all_conditions_are_matched(conditions, context)
682
- elsif o[:segments]
683
- segments = datafile.parse_segments_if_stringified(o[:segments])
684
- datafile.all_segments_are_matched(segments, context)
685
- else
686
- false
687
- end
688
- end
685
+ override_index = overrides.find_index { |o| variable_override_matches?(o, datafile, context, options) }
689
686
 
690
687
  unless override_index.nil?
691
688
  override = overrides[override_index]
@@ -700,7 +697,8 @@ module Featurevisor
700
697
  variable_key: variable_key,
701
698
  variable_schema: variable_schema,
702
699
  variable_value: override[:value],
703
- variable_override_index: override_index
700
+ variable_override_index: override_index,
701
+ variable_override_key: override[:key]
704
702
  }
705
703
 
706
704
  diagnostics.debug("variable override from variation", evaluation)
@@ -5,7 +5,7 @@ require "json"
5
5
  module Featurevisor
6
6
  # Private datafile and matching adapter used by the evaluator.
7
7
  class InstanceEvaluationDataProvider
8
- attr_reader :schema_version, :revision, :featurevisor_version, :segments, :features, :diagnostics, :regex_cache
8
+ attr_reader :schema_version, :revision, :featurevisor_version, :segments, :features, :variables, :diagnostics, :regex_cache
9
9
 
10
10
  # Initialize a new evaluation data provider.
11
11
  # @param options [Hash] Options hash containing datafile and diagnostics
@@ -20,6 +20,7 @@ module Featurevisor
20
20
  @featurevisor_version = datafile[:featurevisorVersion]
21
21
  @segments = (datafile[:segments] || {}).transform_keys(&:to_sym)
22
22
  @features = (datafile[:features] || {}).transform_keys(&:to_sym)
23
+ @variables = (datafile[:variables] || {}).transform_keys(&:to_sym)
23
24
 
24
25
  # Transform nested structures to use symbol keys
25
26
  @features.each do |_key, feature|
@@ -72,7 +73,8 @@ module Featurevisor
72
73
  revision: @revision,
73
74
  featurevisorVersion: @featurevisor_version,
74
75
  segments: @segments,
75
- features: @features
76
+ features: @features,
77
+ variables: @variables
76
78
  }.compact
77
79
  end
78
80
 
@@ -88,6 +90,10 @@ module Featurevisor
88
90
  segment
89
91
  end
90
92
 
93
+ def get_segment_keys
94
+ @segments.keys
95
+ end
96
+
91
97
  # Get all feature keys
92
98
  # @return [Array<Symbol>] Array of feature keys
93
99
  def get_feature_keys
@@ -104,7 +110,9 @@ module Featurevisor
104
110
  # Get variable keys for a feature
105
111
  # @param feature_key [String] Feature key
106
112
  # @return [Array<String>] Array of variable keys
107
- def get_variable_keys(feature_key)
113
+ def get_variable_keys(feature_key = nil)
114
+ return @variables.keys if feature_key.nil?
115
+
108
116
  feature = get_feature(feature_key)
109
117
 
110
118
  return [] unless feature
@@ -112,6 +120,10 @@ module Featurevisor
112
120
  (feature[:variablesSchema] || {}).keys
113
121
  end
114
122
 
123
+ def get_global_variable(variable_key)
124
+ @variables[variable_key.to_sym] || @variables[variable_key]
125
+ end
126
+
115
127
  # Check if a feature has variations
116
128
  # @param feature_key [String] Feature key
117
129
  # @return [Boolean] True if feature has variations