Skip to content

Commit b8fc834

Browse files
mikeproeng37Matt Auerbach
authored andcommitted
Refactor experiment key map (#55)
1 parent e9211e6 commit b8fc834

12 files changed

+112
-189
lines changed

lib/optimizely.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,8 @@ def activate(experiment_key, user_id, attributes = nil)
108108

109109
# Create and dispatch impression event
110110
variation_id = @config.get_variation_id_from_key(experiment_key, variation_key)
111-
impression_event = @event_builder.create_impression_event(experiment_key, variation_id, user_id, attributes)
111+
experiment = @config.get_experiment_from_key(experiment_key)
112+
impression_event = @event_builder.create_impression_event(experiment, variation_id, user_id, attributes)
112113
@logger.log(Logger::INFO,
113114
'Dispatching impression event to URL %s with params %s.' % [impression_event.url,
114115
impression_event.params])

lib/optimizely/audience.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,17 @@ module Optimizely
2020
module Audience
2121
module_function
2222

23-
def user_in_experiment?(config, experiment_key, attributes)
23+
def user_in_experiment?(config, experiment, attributes)
2424
# Determine for given experiment if user satisfies the audiences for the experiment.
2525
#
2626
# config - Representation of the Optimizely project config.
27-
# experiment_key - Key representing experiment for which visitor is to be bucketed.
27+
# experiment - Experiment for which visitor is to be bucketed.
2828
# attributes - Hash representing user attributes which will be used in determining if
2929
# the audience conditions are met.
3030
#
3131
# Returns boolean representing if user satisfies audience conditions for any of the audiences or not.
3232

33-
audience_ids = config.get_audience_ids_for_experiment(experiment_key)
33+
audience_ids = experiment['audienceIds']
3434

3535
# Return true if there are no audiences
3636
return true if audience_ids.empty?

lib/optimizely/bucketer.rb

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -35,17 +35,18 @@ def initialize(config)
3535
@config = config
3636
end
3737

38-
def bucket(experiment_key, user_id)
38+
def bucket(experiment, user_id)
3939
# Determines ID of variation to be shown for a given experiment key and user ID.
4040
#
41-
# experiment_key - String Key representing experiment for which visitor is to be bucketed.
41+
# experiment - Experiment for which visitor is to be bucketed.
4242
# user_id - String ID for user.
4343
#
4444
# Returns String variation ID 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
47-
experiment_id = @config.get_experiment_id(experiment_key)
48-
group_id = @config.get_experiment_group_id(experiment_key)
47+
experiment_id = experiment['id']
48+
experiment_key = experiment['key']
49+
group_id = experiment['groupId']
4950
if group_id
5051
group = @config.group_key_map.fetch(group_id)
5152
if Helpers::Group.random_policy?(group)
@@ -78,11 +79,9 @@ def bucket(experiment_key, user_id)
7879
end
7980
end
8081

81-
bucketing_id = sprintf(BUCKETING_ID_TEMPLATE, user_id: user_id, entity_id: experiment_id)
82-
bucket_value = generate_bucket_value(bucketing_id)
83-
@config.logger.log(Logger::DEBUG, "Assigned variation bucket #{bucket_value} to user '#{user_id}'.")
84-
traffic_allocations = @config.get_traffic_allocation(experiment_key)
85-
variation_id = find_bucket(bucket_value, traffic_allocations)
82+
traffic_allocations = experiment['trafficAllocation']
83+
variation_id = find_bucket(user_id, experiment_id, traffic_allocations)
84+
8685
if variation_id && variation_id != ''
8786
variation_key = @config.get_variation_key_from_id(experiment_key, variation_id)
8887
@config.logger.log(
@@ -103,14 +102,19 @@ def bucket(experiment_key, user_id)
103102

104103
private
105104

106-
def find_bucket(bucket_value, traffic_allocations)
105+
def find_bucket(user_id, parent_id, traffic_allocations)
107106
# Helper function to find the matching entity ID for a given bucketing value in a list of traffic allocations.
108107
#
109-
# bucket_value - Integer bucket value
108+
# user_id - String ID for user
109+
# parent_id - String entity ID to use for bucketing ID
110110
# traffic_allocations - Array of traffic allocations
111111
#
112112
# Returns entity ID corresponding to the provided bucket value or nil if no match is found.
113113

114+
bucketing_id = sprintf(BUCKETING_ID_TEMPLATE, user_id: user_id, entity_id: parent_id)
115+
bucket_value = generate_bucket_value(bucketing_id)
116+
@config.logger.log(Logger::DEBUG, "Assigned bucket #{bucket_value} to user '#{user_id}'.")
117+
114118
traffic_allocations.each do |traffic_allocation|
115119
current_end_of_range = traffic_allocation['endOfRange']
116120
if bucket_value < current_end_of_range

lib/optimizely/decision_service.rb

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,12 +47,16 @@ def get_variation(experiment_key, user_id, attributes = nil)
4747
# Returns variation ID where visitor will be bucketed (nil if experiment is inactive or user does not meet audience conditions)
4848

4949
# Check to make sure experiment is active
50-
unless @config.experiment_running?(experiment_key)
51-
@config.logger.log(Logger::INFO, "Experiment '#{experiment_key}' is not running.")
50+
experiment = @config.get_experiment_from_key(experiment_key)
51+
if experiment.nil?
5252
return nil
5353
end
5454

55-
experiment_id = @config.get_experiment_id(experiment_key)
55+
experiment_id = experiment['id']
56+
unless @config.experiment_running?(experiment)
57+
@config.logger.log(Logger::INFO, "Experiment '#{experiment_key}' is not running.")
58+
return nil
59+
end
5660

5761
# Check if user is in a forced variation
5862
forced_variation_id = get_forced_variation_id(experiment_key, user_id)
@@ -70,7 +74,7 @@ def get_variation(experiment_key, user_id, attributes = nil)
7074
end
7175

7276
# Check audience conditions
73-
unless Audience.user_in_experiment?(@config, experiment_key, attributes)
77+
unless Audience.user_in_experiment?(@config, experiment, attributes)
7478
@config.logger.log(
7579
Logger::INFO,
7680
"User '#{user_id}' does not meet the conditions to be in experiment '#{experiment_key}'."
@@ -79,7 +83,7 @@ def get_variation(experiment_key, user_id, attributes = nil)
7983
end
8084

8185
# Bucket normally
82-
variation_id = @bucketer.bucket(experiment_key, user_id)
86+
variation_id = @bucketer.bucket(experiment, user_id)
8387

8488
# Persist bucketing decision
8589
save_user_profile(user_profile, experiment_id, variation_id)

lib/optimizely/event_builder.rb

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -71,10 +71,10 @@ class EventBuilder < BaseEventBuilder
7171
IMPRESSION_EVENT_ENDPOINT = 'https://logx.optimizely.com/log/decision'
7272
POST_HEADERS = { 'Content-Type' => 'application/json' }
7373

74-
def create_impression_event(experiment_key, variation_id, user_id, attributes)
74+
def create_impression_event(experiment, variation_id, user_id, attributes)
7575
# Create conversion Event to be sent to the logging endpoint.
7676
#
77-
# experiment_key - Experiment for which impression needs to be recorded.
77+
# experiment - Experiment for which impression needs to be recorded.
7878
# variation_id - ID for variation which would be presented to user.
7979
# user_id - ID for user.
8080
# attributes - Hash representing user attributes and values which need to be recorded.
@@ -83,7 +83,7 @@ def create_impression_event(experiment_key, variation_id, user_id, attributes)
8383

8484
@params = {}
8585
add_common_params(user_id, attributes)
86-
add_decision(experiment_key, variation_id)
86+
add_decision(experiment, variation_id)
8787
add_attributes(attributes)
8888
Event.new(:post, IMPRESSION_EVENT_ENDPOINT, @params, POST_HEADERS)
8989
end
@@ -151,8 +151,9 @@ def add_attributes(attributes)
151151
end
152152
end
153153

154-
def add_decision(experiment_key, variation_id)
155-
experiment_id = @config.get_experiment_id(experiment_key)
154+
def add_decision(experiment, variation_id)
155+
experiment_key = experiment['key']
156+
experiment_id = experiment['id']
156157
@params['layerId'] = @config.experiment_key_map[experiment_key]['layerId']
157158
@params['decision'] = {
158159
'variationId' => variation_id,

lib/optimizely/project_config.rb

Lines changed: 6 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -100,28 +100,24 @@ def initialize(datafile, logger, error_handler)
100100
@parsing_succeeded = true
101101
end
102102

103-
def experiment_running?(experiment_key)
103+
def experiment_running?(experiment)
104104
# Determine if experiment corresponding to given key is running
105105
#
106-
# experiment_key - String key representing the experiment
106+
# experiment - Experiment
107107
#
108108
# Returns true if experiment is running
109-
experiment = @experiment_key_map[experiment_key]
110-
return RUNNING_EXPERIMENT_STATUS.include?(experiment['status']) if experiment
111-
@logger.log Logger::ERROR, "Experiment key '#{experiment_key}' is not in datafile."
112-
@error_handler.handle_error InvalidExperimentError
113-
nil
109+
return RUNNING_EXPERIMENT_STATUS.include?(experiment['status'])
114110
end
115111

116-
def get_experiment_id(experiment_key)
112+
def get_experiment_from_key(experiment_key)
117113
# Retrieves experiment ID for a given key
118114
#
119115
# experiment_key - String key representing the experiment
120116
#
121-
# Returns String ID
117+
# Returns Experiment
122118

123119
experiment = @experiment_key_map[experiment_key]
124-
return experiment['id'] if experiment
120+
return experiment if experiment
125121
@logger.log Logger::ERROR, "Experiment key '#{experiment_key}' is not in datafile."
126122
@error_handler.handle_error InvalidExperimentError
127123
nil
@@ -155,34 +151,6 @@ def get_experiment_ids_for_event(event_key)
155151
[]
156152
end
157153

158-
def get_traffic_allocation(experiment_key)
159-
# Retrieves traffic allocation for a given experiment Key
160-
#
161-
# experiment_key - String Key representing the experiment
162-
#
163-
# Returns traffic allocation for the experiment or nil
164-
165-
experiment = @experiment_key_map[experiment_key]
166-
return experiment['trafficAllocation'] if experiment
167-
@logger.log Logger::ERROR, "Experiment key '#{experiment_key}' is not in datafile."
168-
@error_handler.handle_error InvalidExperimentError
169-
nil
170-
end
171-
172-
def get_audience_ids_for_experiment(experiment_key)
173-
# Get audience IDs for the experiment
174-
#
175-
# experiment_key - Experiment key for which audience IDs are to be determined
176-
#
177-
# Returns audience IDs corresponding to the experiment.
178-
179-
experiment = @experiment_key_map[experiment_key]
180-
return experiment['audienceIds'] if experiment
181-
@logger.log Logger::ERROR, "Experiment key '#{experiment_key}' is not in datafile."
182-
@error_handler.handle_error InvalidExperimentError
183-
nil
184-
end
185-
186154
def get_audience_conditions_from_id(audience_id)
187155
# Get audience conditions for the provided audience ID
188156
#
@@ -254,13 +222,6 @@ def get_forced_variations(experiment_key)
254222
@error_handler.handle_error InvalidExperimentError
255223
end
256224

257-
def get_experiment_group_id(experiment_key)
258-
experiment = @experiment_key_map[experiment_key]
259-
return experiment['groupId'] if experiment
260-
@logger.log Logger::ERROR, "Experiment key '#{experiment_key}' is not in datafile."
261-
@error_handler.handle_error InvalidExperimentError
262-
end
263-
264225
def get_attribute_id(attribute_key)
265226
attribute = @attribute_key_map[attribute_key]
266227
return attribute['id'] if attribute
@@ -269,19 +230,6 @@ def get_attribute_id(attribute_key)
269230
nil
270231
end
271232

272-
def user_in_forced_variation?(experiment_key, user_id)
273-
# Determines if a given user is in a forced variation
274-
#
275-
# experiment_key - String experiment key
276-
# user_id - String user ID
277-
#
278-
# Returns true if user is in a forced variation
279-
280-
forced_variations = get_forced_variations(experiment_key)
281-
return forced_variations.include?(user_id) if forced_variations
282-
false
283-
end
284-
285233
def parsing_succeeded?
286234
# Helper method to determine if parsing the datafile was successful.
287235
#
@@ -308,8 +256,6 @@ def variation_id_exists?(experiment_id, variation_id)
308256
return false
309257
end
310258

311-
@logger.log Logger::ERROR, "Experiment ID '#{experiment_id}' is not in datafile."
312-
@error_handler.handle_error InvalidExperimentError
313259
false
314260
end
315261

spec/audience_spec.rb

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,27 +26,26 @@
2626
end
2727

2828
it 'should return true for user_in_experiment? if there are no audiences and no attributes' do
29-
expect(@project_instance.config).to receive(:get_audience_ids_for_experiment)
30-
.with('test_experiment').and_return([])
29+
experiment = @project_instance.config.experiment_key_map['test_experiment']
3130
expect(Optimizely::Audience.user_in_experiment?(@project_instance.config,
32-
'test_experiment',
31+
experiment,
3332
nil)).to be true
3433
end
3534

3635
it 'should return true for user_in_experiment? if there are no audiences and there are attributes' do
37-
expect(@project_instance.config).to receive(:get_audience_ids_for_experiment)
38-
.with('test_experiment').and_return([])
36+
experiment = @project_instance.config.experiment_key_map['test_experiment']
3937
user_attributes = {
4038
'browser_type' => 'firefox'
4139
}
4240
expect(Optimizely::Audience.user_in_experiment?(@project_instance.config,
43-
'test_experiment',
41+
experiment,
4442
user_attributes)).to be true
4543
end
4644

4745
it 'should return false for user_in_experiment? if there are audiences but no attributes' do
46+
experiment = @project_instance.config.experiment_key_map['test_experiment_with_audience']
4847
expect(Optimizely::Audience.user_in_experiment?(@project_instance.config,
49-
'test_experiment_with_audience',
48+
experiment,
5049
nil)).to be false
5150
end
5251

@@ -55,18 +54,19 @@
5554
'browser_type' => 'firefox'
5655
}
5756

57+
experiment = @project_instance.config.experiment_key_map['test_experiment_with_audience']
5858
expect(Optimizely::Audience.user_in_experiment?(@project_instance.config,
59-
'test_experiment_with_audience',
59+
experiment,
6060
user_attributes)).to be true
6161
end
6262

6363
it 'should return false for user_in_experiment? if the audience conditions are not met' do
6464
user_attributes = {
6565
'browser_type' => 'chrome'
6666
}
67-
67+
experiment = @project_instance.config.experiment_key_map['test_experiment_with_audience']
6868
expect(Optimizely::Audience.user_in_experiment?(@project_instance.config,
69-
'test_experiment_with_audience',
69+
experiment,
7070
user_attributes)).to be false
7171
end
7272
end

0 commit comments

Comments
 (0)