Skip to content

Commit 4ecb13a

Browse files
authored
Feature variable accessors (#58)
1 parent f8fac58 commit 4ecb13a

File tree

9 files changed

+698
-11
lines changed

9 files changed

+698
-11
lines changed

lib/optimizely.rb

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,10 @@
1919
require_relative 'optimizely/event_builder'
2020
require_relative 'optimizely/event_dispatcher'
2121
require_relative 'optimizely/exceptions'
22+
require_relative 'optimizely/helpers/constants'
2223
require_relative 'optimizely/helpers/group'
2324
require_relative 'optimizely/helpers/validator'
25+
require_relative 'optimizely/helpers/variable_type'
2426
require_relative 'optimizely/logger'
2527
require_relative 'optimizely/project_config'
2628

@@ -237,8 +239,154 @@ def is_feature_enabled(feature_flag_key, user_id, attributes = nil)
237239
false
238240
end
239241

242+
def get_feature_variable_string(feature_flag_key, variable_key, user_id, attributes = nil)
243+
# Get the String value of the specified variable in the feature flag.
244+
#
245+
# feature_flag_key - String key of feature flag the variable belongs to
246+
# variable_key - String key of variable for which we are getting the string value
247+
# user_id - String user ID
248+
# attributes - Hash representing visitor attributes and values which need to be recorded.
249+
#
250+
# Returns the string variable value.
251+
# Returns nil if the feature flag or variable are not found.
252+
253+
variable_value = get_feature_variable_for_type(
254+
feature_flag_key,
255+
variable_key,
256+
Optimizely::Helpers::Constants::VARIABLE_TYPES["STRING"],
257+
user_id,
258+
attributes
259+
)
260+
261+
return variable_value
262+
end
263+
264+
def get_feature_variable_boolean(feature_flag_key, variable_key, user_id, attributes = nil)
265+
# Get the Boolean value of the specified variable in the feature flag.
266+
#
267+
# feature_flag_key - String key of feature flag the variable belongs to
268+
# variable_key - String key of variable for which we are getting the string value
269+
# user_id - String user ID
270+
# attributes - Hash representing visitor attributes and values which need to be recorded.
271+
#
272+
# Returns the boolean variable value.
273+
# Returns nil if the feature flag or variable are not found.
274+
275+
variable_value = get_feature_variable_for_type(
276+
feature_flag_key,
277+
variable_key,
278+
Optimizely::Helpers::Constants::VARIABLE_TYPES["BOOLEAN"],
279+
user_id,
280+
attributes
281+
)
282+
283+
return variable_value
284+
end
285+
286+
def get_feature_variable_double(feature_flag_key, variable_key, user_id, attributes = nil)
287+
# Get the Double value of the specified variable in the feature flag.
288+
#
289+
# feature_flag_key - String key of feature flag the variable belongs to
290+
# variable_key - String key of variable for which we are getting the string value
291+
# user_id - String user ID
292+
# attributes - Hash representing visitor attributes and values which need to be recorded.
293+
#
294+
# Returns the double variable value.
295+
# Returns nil if the feature flag or variable are not found.
296+
297+
variable_value = get_feature_variable_for_type(
298+
feature_flag_key,
299+
variable_key,
300+
Optimizely::Helpers::Constants::VARIABLE_TYPES["DOUBLE"],
301+
user_id,
302+
attributes
303+
)
304+
305+
return variable_value
306+
end
307+
308+
def get_feature_variable_integer(feature_flag_key, variable_key, user_id, attributes = nil)
309+
# Get the Integer value of the specified variable in the feature flag.
310+
#
311+
# feature_flag_key - String key of feature flag the variable belongs to
312+
# variable_key - String key of variable for which we are getting the string value
313+
# user_id - String user ID
314+
# attributes - Hash representing visitor attributes and values which need to be recorded.
315+
#
316+
# Returns the integer variable value.
317+
# Returns nil if the feature flag or variable are not found.
318+
319+
variable_value = get_feature_variable_for_type(
320+
feature_flag_key,
321+
variable_key,
322+
Optimizely::Helpers::Constants::VARIABLE_TYPES["INTEGER"],
323+
user_id,
324+
attributes
325+
)
326+
327+
return variable_value
328+
end
329+
240330
private
241331

332+
def get_feature_variable_for_type(feature_flag_key, variable_key, variable_type, user_id, attributes = nil)
333+
# Get the variable value for the given feature variable and cast it to the specified type
334+
# The default value is returned if the feature flag is not enabled for the user.
335+
#
336+
# feature_flag_key - String key of feature flag the variable belongs to
337+
# variable_key - String key of variable for which we are getting the string value
338+
# variable_type - String requested type for feature variable
339+
# user_id - String user ID
340+
# attributes - Hash representing visitor attributes and values which need to be recorded.
341+
#
342+
# Returns the type-casted variable value.
343+
# Returns nil if the feature flag or variable are not found.
344+
345+
feature_flag = @config.get_feature_flag_from_key(feature_flag_key)
346+
unless feature_flag
347+
@logger.log(Logger::INFO, "No feature flag was found for key '#{feature_flag_key}'.")
348+
return nil
349+
end
350+
351+
variable_value = nil
352+
variable = @config.get_feature_variable(feature_flag, variable_key)
353+
unless variable.nil?
354+
variable_value = variable['defaultValue']
355+
356+
decision = @decision_service.get_variation_for_feature(feature_flag, user_id, attributes)
357+
unless decision
358+
@logger.log(Logger::INFO,
359+
"User '#{user_id}' was not bucketed into any variation for feature flag '#{feature_flag_key}'. Returning the default variable value '#{variable_value}'.")
360+
else
361+
variation = decision['variation']
362+
variation_variable_usages = @config.variation_id_to_variable_usage_map[variation['id']]
363+
variable_id = variable['id']
364+
unless variation_variable_usages.key?(variable_id)
365+
variation_key = variation['key']
366+
@logger.log(Logger::DEBUG,
367+
"Variable '#{variable_key}' is not used in variation '#{variation_key}'. Returning the default variable value '#{variable_value}'."
368+
)
369+
else
370+
variable_value = variation_variable_usages[variable_id]['value']
371+
@logger.log(Logger::INFO,
372+
"Got variable value '#{variable_value}' for variable '#{variable_key}' of feature flag '#{feature_flag_key}'.")
373+
end
374+
end
375+
end
376+
377+
unless variable_value.nil?
378+
actual_variable_type = variable['type']
379+
unless variable_type == actual_variable_type
380+
@logger.log(Logger::WARN,
381+
"Requested variable type '#{variable_type}' but variable '#{variable_key}' is of type '#{actual_variable_type}'.")
382+
end
383+
384+
variable_value = Helpers::VariableType.cast_value_to_type(variable_value, variable_type, @logger)
385+
end
386+
387+
return variable_value
388+
end
389+
242390
def get_valid_experiments_for_event(event_key, user_id, attributes)
243391
# Get the experiments that we should be tracking for the given event.
244392
#

lib/optimizely/helpers/constants.rb

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,13 @@ module Constants
298298
'revision'
299299
]
300300
}
301+
302+
VARIABLE_TYPES = {
303+
'BOOLEAN' => 'boolean',
304+
'DOUBLE' => 'double',
305+
'INTEGER' => 'integer',
306+
'STRING' => 'string'
307+
}
301308
end
302309
end
303310
end
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
#
2+
# Copyright 2017, Optimizely and contributors
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
#
16+
require 'optimizely/logger'
17+
18+
module Optimizely
19+
module Helpers
20+
module VariableType
21+
module_function
22+
23+
def cast_value_to_type(value, variable_type, logger)
24+
# Attempts to cast the given value to the specified type
25+
#
26+
# value - The string value to cast
27+
# variable_type - String variable type
28+
#
29+
# Returns the cast value or nil if not able to cast
30+
return_value = nil
31+
32+
case variable_type
33+
when "boolean"
34+
return_value = value == "true"
35+
when "double"
36+
begin
37+
return_value = Float(value)
38+
rescue => e
39+
logger.log(Logger::ERROR, "Unable to cast variable value '#{value}' to type '#{variable_type}': #{e.message}.")
40+
end
41+
when "integer"
42+
begin
43+
return_value = Integer(value)
44+
rescue => e
45+
logger.log(Logger::ERROR, "Unable to cast variable value '#{value}' to type '#{variable_type}': #{e.message}.")
46+
end
47+
else
48+
# default case is string
49+
return_value = value
50+
end
51+
52+
return return_value
53+
end
54+
end
55+
end
56+
end

lib/optimizely/project_config.rb

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ class ProjectConfig
4848
attr_reader :experiment_id_map
4949
attr_reader :experiment_key_map
5050
attr_reader :feature_flag_key_map
51+
attr_reader :feature_variable_key_map
5152
attr_reader :group_key_map
5253
attr_reader :rollout_id_map
5354
attr_reader :rollout_experiment_id_map
@@ -129,6 +130,10 @@ def initialize(datafile, logger, error_handler)
129130
end
130131
end
131132
@feature_flag_key_map = generate_key_map(@feature_flags, 'key')
133+
@feature_variable_key_map = {}
134+
@feature_flag_key_map.each do |key, feature_flag|
135+
@feature_variable_key_map[key] = generate_key_map(feature_flag['variables'], 'key')
136+
end
132137
@parsing_succeeded = true
133138
end
134139

@@ -303,6 +308,20 @@ def get_feature_flag_from_key(feature_flag_key)
303308
nil
304309
end
305310

311+
def get_feature_variable(feature_flag, variable_key)
312+
# Retrieves the variable with the given key for the given feature
313+
#
314+
# feature_flag - The feature flag for which we are retrieving the variable
315+
# variable_key - String variable key
316+
#
317+
# Returns variable if found, otherwise nil
318+
feature_flag_key = feature_flag['key']
319+
variable = @feature_variable_key_map[feature_flag_key][variable_key]
320+
return variable if variable
321+
@logger.log Logger::ERROR, "No feature variable was found for key '#{variable_key}' in feature flag '#{feature_flag_key}'."
322+
nil
323+
end
324+
306325
def get_rollout_from_id(rollout_id)
307326
# Retrieves the rollout with the given ID
308327
#

spec/decision_service_spec.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -269,11 +269,11 @@
269269
describe 'when the feature flag\'s experiment ids array is empty' do
270270
it 'should return nil and log a message' do
271271
user_attributes = {}
272-
feature_flag = config.feature_flag_key_map['double_single_variable_feature']
272+
feature_flag = config.feature_flag_key_map['empty_feature']
273273
expect(decision_service.get_variation_for_feature_experiment(feature_flag, 'user_1', user_attributes)).to eq(nil)
274274

275275
expect(spy_logger).to have_received(:log).once
276-
.with(Logger::DEBUG, "The feature flag 'double_single_variable_feature' is not used in any experiments.")
276+
.with(Logger::DEBUG, "The feature flag 'empty_feature' is not used in any experiments.")
277277
end
278278
end
279279

0 commit comments

Comments
 (0)