Skip to content

Commit eb2d81e

Browse files
authored
Release version 1.2.0 (#44)
2 parents ed00e31 + 9bbe6b8 commit eb2d81e

16 files changed

+442
-1203
lines changed

CHANGELOG

+7
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
-------------------------------------------------------------------------------
2+
1.2.0
3+
* Remove support for datafile version 1.
4+
* Refactor order of bucketing operations.
5+
* Always use Event Builder V2.
6+
-------------------------------------------------------------------------------
7+
18
-------------------------------------------------------------------------------
29
1.1.2
310
* Send name of event tags instead of event ID.

lib/optimizely.rb

+79-51
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#
2-
# Copyright 2016, Optimizely and contributors
2+
# Copyright 2016-2017, Optimizely and contributors
33
#
44
# Licensed under the Apache License, Version 2.0 (the "License");
55
# you may not use this file except in compliance with the License.
@@ -37,11 +37,6 @@ class Project
3737
attr_accessor :logger
3838
attr_accessor :error_handler
3939

40-
EVENT_BUILDERS_BY_VERSION = {
41-
Optimizely::V1_CONFIG_VERSION => EventBuilderV1,
42-
Optimizely::V2_CONFIG_VERSION => EventBuilderV2
43-
}
44-
4540
def initialize(datafile, event_dispatcher = nil, logger = nil, error_handler = nil, skip_json_validation = false)
4641
# Constructor for Projects.
4742
#
@@ -58,7 +53,7 @@ def initialize(datafile, event_dispatcher = nil, logger = nil, error_handler = n
5853
@event_dispatcher = event_dispatcher || EventDispatcher.new
5954

6055
begin
61-
validate_inputs(datafile, skip_json_validation)
56+
validate_instantiation_options(datafile, skip_json_validation)
6257
rescue InvalidInputError => e
6358
@is_valid = false
6459
logger = SimpleLogger.new
@@ -75,14 +70,15 @@ def initialize(datafile, event_dispatcher = nil, logger = nil, error_handler = n
7570
return
7671
end
7772

78-
begin
79-
@bucketer = Bucketer.new(@config)
80-
@event_builder = EVENT_BUILDERS_BY_VERSION[@config.version].new(@config, @bucketer)
81-
rescue
73+
unless @config.parsing_succeeded?
8274
@is_valid = false
8375
logger = SimpleLogger.new
84-
logger.log(Logger::ERROR, InvalidDatafileVersionError.new)
76+
logger.log(Logger::ERROR, InvalidDatafileVersionError.new.message)
77+
return
8578
end
79+
80+
@bucketer = Bucketer.new(@config)
81+
@event_builder = EventBuilderV2.new(@config)
8682
end
8783

8884
def activate(experiment_key, user_id, attributes = nil)
@@ -101,24 +97,15 @@ def activate(experiment_key, user_id, attributes = nil)
10197
return nil
10298
end
10399

104-
if attributes && !attributes_valid?(attributes)
105-
@logger.log(Logger::INFO, "Not activating user '#{user_id}'.")
106-
return nil
107-
end
108-
109-
unless preconditions_valid?(experiment_key, user_id, attributes)
110-
@logger.log(Logger::INFO, "Not activating user '#{user_id}'.")
111-
return nil
112-
end
113-
114-
variation_id = @bucketer.bucket(experiment_key, user_id)
100+
variation_key = get_variation(experiment_key, user_id, attributes)
115101

116-
if not variation_id
102+
if variation_key.nil?
117103
@logger.log(Logger::INFO, "Not activating user '#{user_id}'.")
118104
return nil
119105
end
120106

121107
# Create and dispatch impression event
108+
variation_id = @config.get_variation_id_from_key(experiment_key, variation_key)
122109
impression_event = @event_builder.create_impression_event(experiment_key, variation_id, user_id, attributes)
123110
@logger.log(Logger::INFO,
124111
'Dispatching impression event to URL %s with params %s.' % [impression_event.url,
@@ -129,7 +116,7 @@ def activate(experiment_key, user_id, attributes = nil)
129116
@logger.log(Logger::ERROR, "Unable to dispatch impression event. Error: #{e}")
130117
end
131118

132-
@config.get_variation_key_from_id(experiment_key, variation_id)
119+
variation_key
133120
end
134121

135122
def get_variation(experiment_key, user_id, attributes = nil)
@@ -148,24 +135,34 @@ def get_variation(experiment_key, user_id, attributes = nil)
148135
return nil
149136
end
150137

151-
if attributes && !attributes_valid?(attributes)
138+
unless preconditions_valid?(experiment_key, attributes)
152139
@logger.log(Logger::INFO, "Not activating user '#{user_id}.")
153140
return nil
154141
end
155142

156-
unless preconditions_valid?(experiment_key, user_id, attributes)
157-
@logger.log(Logger::INFO, "Not activating user '#{user_id}.")
143+
variation_id = @bucketer.get_forced_variation_id(experiment_key, user_id)
144+
145+
unless variation_id.nil?
146+
return @config.get_variation_key_from_id(experiment_key, variation_id)
147+
end
148+
149+
unless Audience.user_in_experiment?(@config, experiment_key, attributes)
150+
@logger.log(Logger::INFO,
151+
"User '#{user_id}' does not meet the conditions to be in experiment '#{experiment_key}'.")
158152
return nil
159153
end
160154

161155
variation_id = @bucketer.bucket(experiment_key, user_id)
162-
@config.get_variation_key_from_id(experiment_key, variation_id)
156+
unless variation_id.nil?
157+
return @config.get_variation_key_from_id(experiment_key, variation_id)
158+
end
159+
nil
163160
end
164161

165162
def track(event_key, user_id, attributes = nil, event_tags = nil)
166163
# Send conversion event to Optimizely.
167164
#
168-
# event_key - Goal key representing the event which needs to be recorded.
165+
# event_key - Event key representing the event which needs to be recorded.
169166
# user_id - String ID for user.
170167
# attributes - Hash representing visitor attributes and values which need to be recorded.
171168
# event_tags - Hash representing metadata associated with the event.
@@ -183,34 +180,26 @@ def track(event_key, user_id, attributes = nil, event_tags = nil)
183180
@logger.log(Logger::WARN, 'Event value is deprecated in track call. Use event tags to pass in revenue value instead.')
184181
end
185182

186-
return nil if attributes && !attributes_valid?(attributes)
187-
return nil if event_tags && !event_tags_valid?(event_tags)
183+
return nil unless user_inputs_valid?(attributes, event_tags)
188184

189-
experiment_ids = @config.get_experiment_ids_for_goal(event_key)
185+
experiment_ids = @config.get_experiment_ids_for_event(event_key)
190186
if experiment_ids.empty?
191187
@config.logger.log(Logger::INFO, "Not tracking user '#{user_id}'.")
192188
return nil
193189
end
194190

195191
# Filter out experiments that are not running or that do not include the user in audience conditions
196-
valid_experiment_keys = []
197-
experiment_ids.each do |experiment_id|
198-
experiment_key = @config.experiment_id_map[experiment_id]['key']
199-
unless preconditions_valid?(experiment_key, user_id, attributes)
200-
@config.logger.log(Logger::INFO, "Not tracking user '#{user_id}' for experiment '#{experiment_key}'.")
201-
next
202-
end
203-
valid_experiment_keys.push(experiment_key)
204-
end
192+
193+
experiment_variation_map = get_valid_experiments_for_event(event_key, user_id, attributes)
205194

206195
# Don't track events without valid experiments attached
207-
if valid_experiment_keys.empty?
196+
if experiment_variation_map.empty?
208197
@logger.log(Logger::INFO, "There are no valid experiments for event '#{event_key}' to track.")
209198
return nil
210199
end
211200

212201
conversion_event = @event_builder.create_conversion_event(event_key, user_id, attributes,
213-
event_tags, valid_experiment_keys)
202+
event_tags, experiment_variation_map)
214203
@logger.log(Logger::INFO,
215204
'Dispatching conversion event to URL %s with params %s.' % [conversion_event.url,
216205
conversion_event.params])
@@ -223,7 +212,35 @@ def track(event_key, user_id, attributes = nil, event_tags = nil)
223212

224213
private
225214

226-
def preconditions_valid?(experiment_key, user_id, attributes)
215+
def get_valid_experiments_for_event(event_key, user_id, attributes)
216+
# Get the experiments that we should be tracking for the given event.
217+
#
218+
# event_key - Event key representing the event which needs to be recorded.
219+
# user_id - String ID for user.
220+
# attributes - Map of attributes of the user.
221+
#
222+
# Returns Map where each object contains the ID of the experiment to track and the ID of the variation the user
223+
# is bucketed into.
224+
225+
valid_experiments = {}
226+
experiment_ids = @config.get_experiment_ids_for_event(event_key)
227+
experiment_ids.each do |experiment_id|
228+
experiment_key = @config.get_experiment_key(experiment_id)
229+
variation_key = get_variation(experiment_key, user_id, attributes)
230+
231+
if variation_key.nil?
232+
@logger.log(Logger::INFO, "Not tracking user '#{user_id}' for experiment '#{experiment_key}'.")
233+
next
234+
end
235+
236+
variation_id = @config.get_variation_id_from_key(experiment_key, variation_key)
237+
valid_experiments[experiment_id] = variation_id
238+
end
239+
240+
valid_experiments
241+
end
242+
243+
def preconditions_valid?(experiment_key, attributes = nil, event_tags = nil)
227244
# Validates preconditions for bucketing a user.
228245
#
229246
# experiment_key - String key for an experiment.
@@ -232,18 +249,29 @@ def preconditions_valid?(experiment_key, user_id, attributes)
232249
#
233250
# Returns boolean representing whether all preconditions are valid.
234251

252+
return false unless user_inputs_valid?(attributes, event_tags)
253+
235254
unless @config.experiment_running?(experiment_key)
236255
@logger.log(Logger::INFO, "Experiment '#{experiment_key}' is not running.")
237256
return false
238257
end
239258

240-
if @config.user_in_forced_variation?(experiment_key, user_id)
241-
return true
259+
true
260+
end
261+
262+
def user_inputs_valid?(attributes = nil, event_tags = nil)
263+
# Helper method to validate user inputs.
264+
#
265+
# attributes - Dict representing user attributes.
266+
# event_tags - Dict representing metadata associated with an event.
267+
#
268+
# Returns boolean True if inputs are valid. False otherwise.
269+
270+
if !attributes.nil? && !attributes_valid?(attributes)
271+
return false
242272
end
243273

244-
unless Audience.user_in_experiment?(@config, experiment_key, attributes)
245-
@logger.log(Logger::INFO,
246-
"User '#{user_id}' does not meet the conditions to be in experiment '#{experiment_key}'.")
274+
if !event_tags.nil? && !event_tags_valid?(event_tags)
247275
return false
248276
end
249277

@@ -268,7 +296,7 @@ def event_tags_valid?(event_tags)
268296
true
269297
end
270298

271-
def validate_inputs(datafile, skip_json_validation)
299+
def validate_instantiation_options(datafile, skip_json_validation)
272300
unless skip_json_validation
273301
raise InvalidInputError.new('datafile') unless Helpers::Validator.datafile_valid?(datafile)
274302
end

lib/optimizely/bucketer.rb

+31-18
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#
2-
# Copyright 2016, Optimizely and contributors
2+
# Copyright 2016-2017, Optimizely and contributors
33
#
44
# Licensed under the Apache License, Version 2.0 (the "License");
55
# you may not use this file except in compliance with the License.
@@ -43,23 +43,6 @@ def bucket(experiment_key, user_id)
4343
#
4444
# Returns String variation ID in which visitor with ID user_id has been placed. Nil if no variation.
4545

46-
# handle forced variations if applicable
47-
forced_variations = @config.get_forced_variations(experiment_key)
48-
forced_variation_key = forced_variations[user_id]
49-
if forced_variation_key
50-
forced_variation_id = @config.get_variation_id_from_key(experiment_key, forced_variation_key)
51-
if forced_variation_id
52-
@config.logger.log(Logger::INFO, "User '#{user_id}' is forced in variation '#{forced_variation_key}'.")
53-
return forced_variation_id
54-
else
55-
@config.logger.log(
56-
Logger::INFO,
57-
"Variation key '#{forced_variation_key}' is not in datafile. Not activating user '#{user_id}'."
58-
)
59-
return nil
60-
end
61-
end
62-
6346
# check if experiment is in a group; if so, check if user is bucketed into specified experiment
6447
experiment_id = @config.get_experiment_id(experiment_key)
6548
group_id = @config.get_experiment_group_id(experiment_key)
@@ -118,6 +101,36 @@ def bucket(experiment_key, user_id)
118101
nil
119102
end
120103

104+
def get_forced_variation_id(experiment_key, user_id)
105+
# Determine if a user is forced into a variation for the given experiment and return the id of that variation.
106+
#
107+
# experiment_key - Key representing the experiment for which user is to be bucketed.
108+
# user_id - ID for the user.
109+
#
110+
# Returns variation ID in which the user with ID user_id is forced into. Nil if no variation.
111+
112+
forced_variations = @config.get_forced_variations(experiment_key)
113+
114+
return nil unless forced_variations
115+
116+
forced_variation_key = forced_variations[user_id]
117+
118+
return nil unless forced_variation_key
119+
120+
forced_variation_id = @config.get_variation_id_from_key(experiment_key, forced_variation_key)
121+
122+
unless forced_variation_id
123+
@config.logger.log(
124+
Logger::INFO,
125+
"Variation key '#{forced_variation_key}' is not in datafile. Not activating user '#{user_id}'."
126+
)
127+
return nil
128+
end
129+
130+
@config.logger.log(Logger::INFO, "User '#{user_id}' is forced in variation '#{forced_variation_key}'.")
131+
forced_variation_id
132+
end
133+
121134
private
122135

123136
def find_bucket(bucket_value, traffic_allocations)

0 commit comments

Comments
 (0)