Skip to content

Commit 28da3af

Browse files
rashidspmikeproeng37
authored andcommitted
fixes variable type bug (#73)
1 parent ef897e2 commit 28da3af

File tree

4 files changed

+117
-64
lines changed

4 files changed

+117
-64
lines changed

lib/optimizely.rb

Lines changed: 38 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -378,51 +378,64 @@ def get_feature_variable_for_type(feature_flag_key, variable_key, variable_type,
378378
# attributes - Hash representing visitor attributes and values which need to be recorded.
379379
#
380380
# Returns the type-casted variable value.
381-
# Returns nil if the feature flag or variable are not found.
381+
# Returns nil if the feature flag or variable or user ID is empty
382+
# in case of variable type mismatch
383+
384+
unless feature_flag_key
385+
@logger.log(Logger::ERROR, "Feature flag key cannot be empty.")
386+
return nil
387+
end
388+
389+
unless variable_key
390+
@logger.log(Logger::ERROR, "Variable key cannot be empty.")
391+
return nil
392+
end
393+
394+
unless user_id
395+
@logger.log(Logger::ERROR, "User ID cannot be empty.")
396+
return nil
397+
end
382398

383399
feature_flag = @config.get_feature_flag_from_key(feature_flag_key)
384400
unless feature_flag
385401
@logger.log(Logger::INFO, "No feature flag was found for key '#{feature_flag_key}'.")
386402
return nil
387403
end
388-
389-
variable_value = nil
404+
390405
variable = @config.get_feature_variable(feature_flag, variable_key)
391-
unless variable.nil?
392-
variable_value = variable['defaultValue']
393406

407+
# Error message logged in ProjectConfig- get_feature_flag_from_key
408+
return nil if variable.nil?
409+
410+
# Returns nil if type differs
411+
if variable['type'] != variable_type
412+
@logger.log(Logger::WARN,
413+
"Requested variable as type '#{variable_type}' but variable '#{variable_key}' is of type '#{variable['type']}'.")
414+
return nil
415+
else
394416
decision = @decision_service.get_variation_for_feature(feature_flag, user_id, attributes)
395-
unless decision
396-
@logger.log(Logger::INFO,
397-
"User '#{user_id}' was not bucketed into any variation for feature flag '#{feature_flag_key}'. Returning the default variable value '#{variable_value}'.")
398-
else
417+
variable_value = variable['defaultValue']
418+
if decision
399419
variation = decision['variation']
400420
variation_variable_usages = @config.variation_id_to_variable_usage_map[variation['id']]
401421
variable_id = variable['id']
402-
unless variation_variable_usages and variation_variable_usages.key?(variable_id)
403-
variation_key = variation['key']
404-
@logger.log(Logger::DEBUG,
405-
"Variable '#{variable_key}' is not used in variation '#{variation_key}'. Returning the default variable value '#{variable_value}'."
406-
)
407-
else
422+
if variation_variable_usages and variation_variable_usages.key?(variable_id)
408423
variable_value = variation_variable_usages[variable_id]['value']
409424
@logger.log(Logger::INFO,
410425
"Got variable value '#{variable_value}' for variable '#{variable_key}' of feature flag '#{feature_flag_key}'.")
426+
else
427+
@logger.log(Logger::DEBUG,
428+
"Variable '#{variable_key}' is not used in variation '#{variation['key']}'. Returning the default variable value '#{variable_value}'.")
411429
end
430+
else
431+
@logger.log(Logger::INFO,
432+
"User '#{user_id}' was not bucketed into any variation for feature flag '#{feature_flag_key}'. Returning the default variable value '#{variable_value}'.")
412433
end
413434
end
414435

415-
unless variable_value.nil?
416-
actual_variable_type = variable['type']
417-
unless variable_type == actual_variable_type
418-
@logger.log(Logger::WARN,
419-
"Requested variable type '#{variable_type}' but variable '#{variable_key}' is of type '#{actual_variable_type}'.")
420-
end
436+
variable_value = Helpers::VariableType.cast_value_to_type(variable_value, variable_type, @logger)
421437

422-
variable_value = Helpers::VariableType.cast_value_to_type(variable_value, variable_type, @logger)
423-
end
424-
425-
return variable_value
438+
variable_value
426439
end
427440

428441
def get_valid_experiments_for_event(event_key, user_id, attributes)

lib/optimizely/decision_service.rb

Lines changed: 30 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
require_relative './bucketer'
1717

1818
module Optimizely
19-
2019
RESERVED_ATTRIBUTE_KEY_BUCKETING_ID = "\$opt_bucketing_id".freeze
2120

2221
class DecisionService
@@ -54,9 +53,7 @@ def get_variation(experiment_key, user_id, attributes = nil)
5453
bucketing_id = get_bucketing_id(user_id, attributes)
5554
# Check to make sure experiment is active
5655
experiment = @config.get_experiment_from_key(experiment_key)
57-
if experiment.nil?
58-
return nil
59-
end
56+
return nil if experiment.nil?
6057

6158
experiment_id = experiment['id']
6259
unless @config.experiment_running?(experiment)
@@ -152,34 +149,33 @@ def get_variation_for_feature_experiment(feature_flag, user_id, attributes = nil
152149
"The feature flag '#{feature_flag_key}' is not used in any experiments."
153150
)
154151
return nil
155-
else
156-
# Evaluate each experiment and return the first bucketed experiment variation
157-
feature_flag['experimentIds'].each do |experiment_id|
158-
experiment = @config.experiment_id_map[experiment_id]
159-
unless experiment
160-
@config.logger.log(
161-
Logger::DEBUG,
162-
"Feature flag experiment with ID '#{experiment_id}' is not in the datafile."
163-
)
164-
return nil
165-
end
166-
experiment_key = experiment['key']
167-
variation_id = get_variation(experiment_key, user_id, attributes)
168-
next unless variation_id
169-
variation = @config.variation_id_map[experiment_key][variation_id]
170-
@config.logger.log(Logger::INFO,
171-
"The user '#{user_id}' is bucketed into experiment '#{experiment_key}' of feature "\
172-
"'#{feature_flag_key}'.")
173-
return {
174-
'experiment' => experiment,
175-
'variation' => variation
176-
}
152+
end
153+
# Evaluate each experiment and return the first bucketed experiment variation
154+
feature_flag['experimentIds'].each do |experiment_id|
155+
experiment = @config.experiment_id_map[experiment_id]
156+
unless experiment
157+
@config.logger.log(
158+
Logger::DEBUG,
159+
"Feature flag experiment with ID '#{experiment_id}' is not in the datafile."
160+
)
161+
return nil
177162
end
178-
@config.logger.log(
179-
Logger::INFO,
180-
"The user '#{user_id}' is not bucketed into any of the experiments on the feature '#{feature_flag_key}'."
181-
)
163+
experiment_key = experiment['key']
164+
variation_id = get_variation(experiment_key, user_id, attributes)
165+
next unless variation_id
166+
variation = @config.variation_id_map[experiment_key][variation_id]
167+
@config.logger.log(Logger::INFO,
168+
"The user '#{user_id}' is bucketed into experiment '#{experiment_key}' of feature "\
169+
"'#{feature_flag_key}'.")
170+
return {
171+
'experiment' => experiment,
172+
'variation' => variation
173+
}
182174
end
175+
@config.logger.log(
176+
Logger::INFO,
177+
"The user '#{user_id}' is not bucketed into any of the experiments on the feature '#{feature_flag_key}'."
178+
)
183179

184180
nil
185181
end
@@ -250,6 +246,7 @@ def get_variation_for_feature_rollout(feature_flag, user_id, attributes = nil)
250246
Logger::DEBUG,
251247
"User '#{user_id}' was excluded due to traffic allocation. Checking 'Everyone Else' rule now."
252248
)
249+
253250
break
254251
end
255252

@@ -262,6 +259,7 @@ def get_variation_for_feature_rollout(feature_flag, user_id, attributes = nil)
262259
Logger::DEBUG,
263260
"User '#{user_id}' was excluded from the 'Everyone Else' rule for feature flag"
264261
)
262+
265263
nil
266264
end
267265

@@ -329,8 +327,8 @@ def get_user_profile(user_id)
329327
# Returns Hash stored user profile (or a default one if lookup fails or user profile service not provided)
330328

331329
user_profile = {
332-
:user_id => user_id,
333-
:experiment_bucket_map => {}
330+
user_id: user_id,
331+
experiment_bucket_map: {}
334332
}
335333

336334
return user_profile unless @user_profile_service

spec/decision_service_spec.rb

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -534,7 +534,6 @@
534534
.with(Logger::DEBUG, "User '#{user_id}' was excluded due to traffic allocation. Checking 'Everyone Else' rule now.")
535535
expect(spy_logger).to have_received(:log).once
536536
.with(Logger::DEBUG, "User '#{user_id}' was excluded from the 'Everyone Else' rule for feature flag")
537-
538537
end
539538
end
540539

spec/project_spec.rb

Lines changed: 49 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -713,7 +713,7 @@ class InvalidErrorHandler; end
713713
end
714714

715715
describe 'and a variable usage instance is found' do
716-
describe 'and the variable type is not a string' do
716+
describe 'and the variable type boolean is not a string' do
717717
it 'should log a warning' do
718718
variation_to_return = project_instance.config.rollout_id_map['166660']['experiments'][0]['variations'][0]
719719
decision_to_return = {
@@ -723,18 +723,34 @@ class InvalidErrorHandler; end
723723
allow(project_instance.decision_service).to receive(:get_variation_for_feature).and_return(decision_to_return)
724724

725725
expect(project_instance.get_feature_variable_string('boolean_single_variable_feature', 'boolean_variable', user_id, user_attributes))
726-
.to eq('true')
726+
.to eq(nil)
727727

728-
expect(spy_logger).to have_received(:log).twice
729728
expect(spy_logger).to have_received(:log).once
730729
.with(
731-
Logger::INFO,
732-
"Got variable value 'true' for variable 'boolean_variable' of feature flag 'boolean_single_variable_feature'."
730+
Logger::WARN,
731+
"Requested variable as type 'string' but variable 'boolean_variable' is of type 'boolean'."
733732
)
733+
end
734+
end
735+
736+
describe 'and the variable type integer is not a string' do
737+
it 'should log a warning' do
738+
integer_feature = project_instance.config.feature_flag_key_map['integer_single_variable_feature']
739+
experiment_to_return = project_instance.config.experiment_id_map[integer_feature['experimentIds'][0]]
740+
variation_to_return = experiment_to_return['variations'][0]
741+
decision_to_return = {
742+
'experiment' => experiment_to_return,
743+
'variation' => variation_to_return
744+
}
745+
allow(project_instance.decision_service).to receive(:get_variation_for_feature).and_return(decision_to_return)
746+
747+
expect(project_instance.get_feature_variable_string('integer_single_variable_feature', 'integer_variable', user_id, user_attributes))
748+
.to eq(nil)
749+
734750
expect(spy_logger).to have_received(:log).once
735751
.with(
736752
Logger::WARN,
737-
"Requested variable type 'string' but variable 'boolean_variable' is of type 'boolean'."
753+
"Requested variable as type 'string' but variable 'integer_variable' is of type 'integer'."
738754
)
739755
end
740756
end
@@ -888,6 +904,33 @@ class InvalidErrorHandler; end
888904
end
889905
end
890906

907+
908+
describe '#get_feature_variable_for_type with empty params' do
909+
user_id = 'test_user'
910+
user_attributes = {}
911+
912+
it 'should return nil if feature_flag_key is nil' do
913+
914+
expect(project_instance.get_feature_variable_integer(nil, 'integer_variable', user_id, user_attributes))
915+
.to eq(nil)
916+
expect(spy_logger).to have_received(:log).once.with(Logger::ERROR,"Feature flag key cannot be empty.")
917+
end
918+
919+
it 'should return nil if variable_key is nil' do
920+
921+
expect(project_instance.get_feature_variable_integer('integer_single_variable_feature', nil, user_id, user_attributes))
922+
.to eq(nil)
923+
expect(spy_logger).to have_received(:log).once.with(Logger::ERROR,"Variable key cannot be empty.")
924+
end
925+
926+
it 'should return nil if user_id is nil' do
927+
928+
expect(project_instance.get_feature_variable_integer('integer_single_variable_feature', 'integer_variable', nil, user_attributes))
929+
.to eq(nil)
930+
expect(spy_logger).to have_received(:log).once.with(Logger::ERROR,"User ID cannot be empty.")
931+
end
932+
end
933+
891934
describe 'when forced variation is used' do
892935
# setForcedVariation on a paused experiment and then call getVariation.
893936
it 'should return null when getVariation is called on a paused experiment after setForcedVariation' do

0 commit comments

Comments
 (0)