Skip to content

Commit 6c29f5b

Browse files
authored
Refactor bucketer return value and fix FF decision bug (#59)
1 parent 4ecb13a commit 6c29f5b

File tree

10 files changed

+45
-33
lines changed

10 files changed

+45
-33
lines changed

CHANGELOG

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
11
-------------------------------------------------------------------------------
2+
2.0.0.beta
3+
* Introduce feature flags and feature rollouts support.
4+
* Introduce variable support via feature flags.
5+
-------------------------------------------------------------------------------
6+
-------------------------------------------------------------------------------
27
1.3.0
38
* Introduce user profile service support.
49
-------------------------------------------------------------------------------

lib/optimizely.rb

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,10 @@ def get_variation(experiment_key, user_id, attributes = nil)
139139
variation_id = @decision_service.get_variation(experiment_key, user_id, attributes)
140140

141141
unless variation_id.nil?
142-
return @config.get_variation_key_from_id(experiment_key, variation_id)
142+
variation = @config.get_variation_from_id(experiment_key, variation_id)
143+
if variation
144+
return variation['key']
145+
end
143146
end
144147
nil
145148
end
@@ -361,7 +364,7 @@ def get_feature_variable_for_type(feature_flag_key, variable_key, variable_type,
361364
variation = decision['variation']
362365
variation_variable_usages = @config.variation_id_to_variable_usage_map[variation['id']]
363366
variable_id = variable['id']
364-
unless variation_variable_usages.key?(variable_id)
367+
unless variation_variable_usages and variation_variable_usages.key?(variable_id)
365368
variation_key = variation['key']
366369
@logger.log(Logger::DEBUG,
367370
"Variable '#{variable_key}' is not used in variation '#{variation_key}'. Returning the default variable value '#{variable_value}'."

lib/optimizely/bucketer.rb

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ def bucket(experiment, user_id)
4141
# experiment - Experiment for which visitor is to be bucketed.
4242
# user_id - String ID for user.
4343
#
44-
# Returns String variation ID in which visitor with ID user_id has been placed. Nil if no variation.
44+
# Returns variation in which visitor with ID user_id has been placed. Nil if no variation.
4545

4646
# check if experiment is in a group; if so, check if user is bucketed into specified experiment
4747
experiment_id = experiment['id']
@@ -78,12 +78,13 @@ def bucket(experiment, user_id)
7878
traffic_allocations = experiment['trafficAllocation']
7979
variation_id = find_bucket(user_id, experiment_id, traffic_allocations)
8080
if variation_id && variation_id != ''
81-
variation_key = @config.get_variation_key_from_id(experiment_key, variation_id)
81+
variation = @config.get_variation_from_id(experiment_key, variation_id)
82+
variation_key = variation ? variation['key'] : nil
8283
@config.logger.log(
8384
Logger::INFO,
8485
"User '#{user_id}' is in variation '#{variation_key}' of experiment '#{experiment_key}'."
8586
)
86-
return variation_id
87+
return variation
8788
end
8889

8990
# Handle the case when the traffic range is empty due to sticky bucketing

lib/optimizely/decision_service.rb

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,8 @@ def get_variation(experiment_key, user_id, attributes = nil)
8383
end
8484

8585
# Bucket normally
86-
variation_id = @bucketer.bucket(experiment, user_id)
86+
variation = @bucketer.bucket(experiment, user_id)
87+
variation_id = variation ? variation['id'] : nil
8788

8889
# Persist bucketing decision
8990
save_user_profile(user_profile, experiment_id, variation_id)
@@ -244,10 +245,6 @@ def get_variation_for_feature_rollout(feature_flag, user_id, attributes = nil)
244245
variation = @bucketer.bucket(experiment, user_id)
245246
unless variation.nil?
246247
variation_key = variation['key']
247-
@config.logger.log(
248-
Logger::DEBUG,
249-
"User '#{user_id}' is in variation '#{variation_key}' of experiment '#{experiment_key}'."
250-
)
251248
return variation
252249
end
253250

lib/optimizely/project_config.rb

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -202,18 +202,18 @@ def get_audience_conditions_from_id(audience_id)
202202
nil
203203
end
204204

205-
def get_variation_key_from_id(experiment_key, variation_id)
206-
# Get variation key given experiment key and variation ID
205+
def get_variation_from_id(experiment_key, variation_id)
206+
# Get variation given experiment key and variation ID
207207
#
208208
# experiment_key - Key representing parent experiment of variation
209209
# variation_id - ID of the variation
210210
#
211-
# Returns key of the variation
211+
# Returns the variation or nil if not found
212212

213213
variation_id_map = @variation_id_map[experiment_key]
214214
if variation_id_map
215215
variation = variation_id_map[variation_id]
216-
return variation['key'] if variation
216+
return variation if variation
217217
@logger.log Logger::ERROR, "Variation id '#{variation_id}' is not in datafile."
218218
@error_handler.handle_error InvalidVariationError
219219
return nil

lib/optimizely/version.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,5 @@
1414
# limitations under the License.
1515
#
1616
module Optimizely
17-
VERSION = '1.3.0'.freeze
17+
VERSION = '2.0.0.beta'.freeze
1818
end

spec/bucketing_spec.rb

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,12 @@ def get_bucketing_id(user_id, entity_id=nil)
3636
experiment = config.get_experiment_from_key('test_experiment')
3737

3838
# Variation 1
39-
expect(bucketer.bucket(experiment, 'test_user')).to eq('111128')
39+
expected_variation_1 = config.get_variation_from_id('test_experiment', '111128')
40+
expect(bucketer.bucket(experiment, 'test_user')).to eq(expected_variation_1)
4041

4142
# Variation 2
42-
expect(bucketer.bucket(experiment, 'test_user')).to eq('111129')
43+
expected_variation_2 = config.get_variation_from_id('test_experiment','111129')
44+
expect(bucketer.bucket(experiment, 'test_user')).to eq(expected_variation_2)
4345

4446
# No matching variation
4547
expect(bucketer.bucket(experiment, 'test_user')).to be_nil
@@ -58,7 +60,8 @@ def get_bucketing_id(user_id, entity_id=nil)
5860
expect(bucketer).to receive(:generate_bucket_value).twice.and_return(3000)
5961

6062
experiment = config.get_experiment_from_key('group1_exp1')
61-
expect(bucketer.bucket(experiment, 'test_user')).to eq('130001')
63+
expected_variation = config.get_variation_from_id('group1_exp1','130001')
64+
expect(bucketer.bucket(experiment, 'test_user')).to eq(expected_variation)
6265
expect(spy_logger).to have_received(:log).exactly(4).times
6366
expect(spy_logger).to have_received(:log).twice
6467
.with(Logger::DEBUG, "Assigned bucket 3000 to user 'test_user'.")
@@ -92,7 +95,8 @@ def get_bucketing_id(user_id, entity_id=nil)
9295
expect(bucketer).to receive(:generate_bucket_value).once.and_return(3000)
9396

9497
experiment = config.get_experiment_from_key('group2_exp1')
95-
expect(bucketer.bucket(experiment, 'test_user')).to eq('144443')
98+
expected_variation = config.get_variation_from_id('group2_exp1','144443')
99+
expect(bucketer.bucket(experiment, 'test_user')).to eq(expected_variation)
96100
expect(spy_logger).to have_received(:log).twice
97101
expect(spy_logger).to have_received(:log)
98102
.with(Logger::DEBUG, "Assigned bucket 3000 to user 'test_user'.")

spec/decision_service_spec.rb

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -429,8 +429,6 @@
429429

430430
expect(spy_logger).to have_received(:log).once
431431
.with(Logger::DEBUG, "User 'user_1' meets conditions for targeting rule '1'.")
432-
expect(spy_logger).to have_received(:log).once
433-
.with(Logger::DEBUG, "User 'user_1' is in variation '177771' of experiment '177770'.")
434432
end
435433
end
436434

spec/project_config_spec.rb

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -662,14 +662,14 @@
662662
end
663663
end
664664

665-
describe 'get_variation_key_from_id' do
665+
describe 'get_variation_from_id' do
666666
it 'should log a message when provided experiment key is invalid' do
667-
config.get_variation_key_from_id('invalid_key', 'some_variation')
667+
config.get_variation_from_id('invalid_key', 'some_variation')
668668
expect(spy_logger).to have_received(:log).with(Logger::ERROR,
669669
"Experiment key 'invalid_key' is not in datafile.")
670670
end
671671
it 'should return nil when provided variation key is invalid' do
672-
expect(config.get_variation_key_from_id('test_experiment', 'invalid_variation')).to eq(nil)
672+
expect(config.get_variation_from_id('test_experiment', 'invalid_variation')).to eq(nil)
673673
end
674674
end
675675

@@ -738,16 +738,16 @@
738738
end
739739
end
740740

741-
describe 'get_variation_key_from_id' do
741+
describe 'get_variation_from_id' do
742742
it 'should raise an error when provided experiment key is invalid' do
743-
expect { config.get_variation_key_from_id('invalid_key', 'some_variation') }
743+
expect { config.get_variation_from_id('invalid_key', 'some_variation') }
744744
.to raise_error(Optimizely::InvalidExperimentError)
745745
end
746746
end
747747

748-
describe 'get_variation_key_from_id' do
748+
describe 'get_variation_from_id' do
749749
it 'should raise an error when provided variation key is invalid' do
750-
expect { config.get_variation_key_from_id('test_experiment', 'invalid_variation') }
750+
expect { config.get_variation_from_id('test_experiment', 'invalid_variation') }
751751
.to raise_error(Optimizely::InvalidVariationError)
752752
end
753753
end

spec/project_spec.rb

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
require 'optimizely/exceptions'
2121
require 'optimizely/version'
2222

23-
describe 'OptimizelyV2' do
23+
describe 'Optimizely' do
2424
let(:config_body) { OptimizelySpec::VALID_CONFIG_BODY }
2525
let(:config_body_JSON) { OptimizelySpec::VALID_CONFIG_BODY_JSON }
2626
let(:config_body_invalid_JSON) { OptimizelySpec::INVALID_CONFIG_BODY_JSON }
@@ -136,7 +136,8 @@ class InvalidErrorHandler; end
136136
}
137137
}
138138

139-
allow(project_instance.decision_service.bucketer).to receive(:bucket).and_return('111128')
139+
variation_to_return = project_instance.config.get_variation_from_id('test_experiment', '111128')
140+
allow(project_instance.decision_service.bucketer).to receive(:bucket).and_return(variation_to_return)
140141
allow(project_instance.event_dispatcher).to receive(:dispatch_event).with(instance_of(Optimizely::Event))
141142
allow(project_instance.config).to receive(:get_audience_ids_for_experiment)
142143
.with('test_experiment')
@@ -175,7 +176,8 @@ class InvalidErrorHandler; end
175176
}
176177
}
177178

178-
allow(project_instance.decision_service.bucketer).to receive(:bucket).and_return('122228')
179+
variation_to_return = project_instance.config.get_variation_from_id('test_experiment_with_audience', '122228')
180+
allow(project_instance.decision_service.bucketer).to receive(:bucket).and_return(variation_to_return)
179181
allow(project_instance.event_dispatcher).to receive(:dispatch_event).with(instance_of(Optimizely::Event))
180182

181183
expect(project_instance.activate('test_experiment_with_audience', 'test_user', 'browser_type' => 'firefox'))
@@ -226,7 +228,8 @@ class InvalidErrorHandler; end
226228
}
227229
}
228230

229-
allow(project_instance.decision_service.bucketer).to receive(:bucket).and_return('111128')
231+
variation_to_return = project_instance.config.get_variation_from_id('test_experiment', '111128')
232+
allow(project_instance.decision_service.bucketer).to receive(:bucket).and_return(variation_to_return)
230233
allow(project_instance.event_dispatcher).to receive(:dispatch_event).with(instance_of(Optimizely::Event))
231234
allow(project_instance.config).to receive(:get_audience_ids_for_experiment)
232235
.with('test_experiment')
@@ -237,7 +240,8 @@ class InvalidErrorHandler; end
237240
end
238241

239242
it 'should log when an exception has occurred during dispatching the impression event' do
240-
allow(project_instance.decision_service.bucketer).to receive(:bucket).and_return('111128')
243+
variation_to_return = project_instance.config.get_variation_from_id('test_experiment', '111128')
244+
allow(project_instance.decision_service.bucketer).to receive(:bucket).and_return(variation_to_return)
241245
allow(project_instance.event_dispatcher).to receive(:dispatch_event).with(any_args).and_raise(RuntimeError)
242246
project_instance.activate('test_experiment', 'test_user')
243247
expect(spy_logger).to have_received(:log).once.with(Logger::ERROR, "Unable to dispatch impression event. Error: RuntimeError")

0 commit comments

Comments
 (0)