optimizely-sdk 1.1.2 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/optimizely.rb +79 -51
- data/lib/optimizely/bucketer.rb +31 -18
- data/lib/optimizely/event_builder.rb +11 -169
- data/lib/optimizely/exceptions.rb +2 -2
- data/lib/optimizely/helpers/constants.rb +1 -278
- data/lib/optimizely/helpers/validator.rb +2 -8
- data/lib/optimizely/project_config.rb +36 -35
- data/lib/optimizely/version.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c8eff682f4c4c99808f246f2828a4e34e276e54b
|
4
|
+
data.tar.gz: 1c4de8ae37ce421199a1d66ea67c5e9ba78e1c41
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 32ad714501f8b55a48201653de322c2f47e33634d323bb653909307cfc289ceff833ba44508b823ecfe0a4ade94cae2aac8ecca5bd2d6ee350d1ec82977a2a69
|
7
|
+
data.tar.gz: 96297561d1ee0a395d35f8b9878ac71b2c097583442bf7327b01db773ef13b32a5aa223c288beeb10f1da75ff4d713baf511286a150bf5a38cd5ea5e5bf2c1cd
|
data/lib/optimizely.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
#
|
2
|
-
# Copyright 2016, Optimizely and contributors
|
2
|
+
# Copyright 2016-2017, Optimizely and contributors
|
3
3
|
#
|
4
4
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
5
|
# you may not use this file except in compliance with the License.
|
@@ -37,11 +37,6 @@ module Optimizely
|
|
37
37
|
attr_accessor :logger
|
38
38
|
attr_accessor :error_handler
|
39
39
|
|
40
|
-
EVENT_BUILDERS_BY_VERSION = {
|
41
|
-
Optimizely::V1_CONFIG_VERSION => EventBuilderV1,
|
42
|
-
Optimizely::V2_CONFIG_VERSION => EventBuilderV2
|
43
|
-
}
|
44
|
-
|
45
40
|
def initialize(datafile, event_dispatcher = nil, logger = nil, error_handler = nil, skip_json_validation = false)
|
46
41
|
# Constructor for Projects.
|
47
42
|
#
|
@@ -58,7 +53,7 @@ module Optimizely
|
|
58
53
|
@event_dispatcher = event_dispatcher || EventDispatcher.new
|
59
54
|
|
60
55
|
begin
|
61
|
-
|
56
|
+
validate_instantiation_options(datafile, skip_json_validation)
|
62
57
|
rescue InvalidInputError => e
|
63
58
|
@is_valid = false
|
64
59
|
logger = SimpleLogger.new
|
@@ -75,14 +70,15 @@ module Optimizely
|
|
75
70
|
return
|
76
71
|
end
|
77
72
|
|
78
|
-
|
79
|
-
@bucketer = Bucketer.new(@config)
|
80
|
-
@event_builder = EVENT_BUILDERS_BY_VERSION[@config.version].new(@config, @bucketer)
|
81
|
-
rescue
|
73
|
+
unless @config.parsing_succeeded?
|
82
74
|
@is_valid = false
|
83
75
|
logger = SimpleLogger.new
|
84
|
-
logger.log(Logger::ERROR, InvalidDatafileVersionError.new)
|
76
|
+
logger.log(Logger::ERROR, InvalidDatafileVersionError.new.message)
|
77
|
+
return
|
85
78
|
end
|
79
|
+
|
80
|
+
@bucketer = Bucketer.new(@config)
|
81
|
+
@event_builder = EventBuilderV2.new(@config)
|
86
82
|
end
|
87
83
|
|
88
84
|
def activate(experiment_key, user_id, attributes = nil)
|
@@ -101,24 +97,15 @@ module Optimizely
|
|
101
97
|
return nil
|
102
98
|
end
|
103
99
|
|
104
|
-
|
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)
|
115
101
|
|
116
|
-
if
|
102
|
+
if variation_key.nil?
|
117
103
|
@logger.log(Logger::INFO, "Not activating user '#{user_id}'.")
|
118
104
|
return nil
|
119
105
|
end
|
120
106
|
|
121
107
|
# Create and dispatch impression event
|
108
|
+
variation_id = @config.get_variation_id_from_key(experiment_key, variation_key)
|
122
109
|
impression_event = @event_builder.create_impression_event(experiment_key, variation_id, user_id, attributes)
|
123
110
|
@logger.log(Logger::INFO,
|
124
111
|
'Dispatching impression event to URL %s with params %s.' % [impression_event.url,
|
@@ -129,7 +116,7 @@ module Optimizely
|
|
129
116
|
@logger.log(Logger::ERROR, "Unable to dispatch impression event. Error: #{e}")
|
130
117
|
end
|
131
118
|
|
132
|
-
|
119
|
+
variation_key
|
133
120
|
end
|
134
121
|
|
135
122
|
def get_variation(experiment_key, user_id, attributes = nil)
|
@@ -148,24 +135,34 @@ module Optimizely
|
|
148
135
|
return nil
|
149
136
|
end
|
150
137
|
|
151
|
-
|
138
|
+
unless preconditions_valid?(experiment_key, attributes)
|
152
139
|
@logger.log(Logger::INFO, "Not activating user '#{user_id}.")
|
153
140
|
return nil
|
154
141
|
end
|
155
142
|
|
156
|
-
|
157
|
-
|
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}'.")
|
158
152
|
return nil
|
159
153
|
end
|
160
154
|
|
161
155
|
variation_id = @bucketer.bucket(experiment_key, user_id)
|
162
|
-
|
156
|
+
unless variation_id.nil?
|
157
|
+
return @config.get_variation_key_from_id(experiment_key, variation_id)
|
158
|
+
end
|
159
|
+
nil
|
163
160
|
end
|
164
161
|
|
165
162
|
def track(event_key, user_id, attributes = nil, event_tags = nil)
|
166
163
|
# Send conversion event to Optimizely.
|
167
164
|
#
|
168
|
-
# event_key -
|
165
|
+
# event_key - Event key representing the event which needs to be recorded.
|
169
166
|
# user_id - String ID for user.
|
170
167
|
# attributes - Hash representing visitor attributes and values which need to be recorded.
|
171
168
|
# event_tags - Hash representing metadata associated with the event.
|
@@ -183,34 +180,26 @@ module Optimizely
|
|
183
180
|
@logger.log(Logger::WARN, 'Event value is deprecated in track call. Use event tags to pass in revenue value instead.')
|
184
181
|
end
|
185
182
|
|
186
|
-
return nil
|
187
|
-
return nil if event_tags && !event_tags_valid?(event_tags)
|
183
|
+
return nil unless user_inputs_valid?(attributes, event_tags)
|
188
184
|
|
189
|
-
experiment_ids = @config.
|
185
|
+
experiment_ids = @config.get_experiment_ids_for_event(event_key)
|
190
186
|
if experiment_ids.empty?
|
191
187
|
@config.logger.log(Logger::INFO, "Not tracking user '#{user_id}'.")
|
192
188
|
return nil
|
193
189
|
end
|
194
190
|
|
195
191
|
# Filter out experiments that are not running or that do not include the user in audience conditions
|
196
|
-
|
197
|
-
|
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)
|
205
194
|
|
206
195
|
# Don't track events without valid experiments attached
|
207
|
-
if
|
196
|
+
if experiment_variation_map.empty?
|
208
197
|
@logger.log(Logger::INFO, "There are no valid experiments for event '#{event_key}' to track.")
|
209
198
|
return nil
|
210
199
|
end
|
211
200
|
|
212
201
|
conversion_event = @event_builder.create_conversion_event(event_key, user_id, attributes,
|
213
|
-
event_tags,
|
202
|
+
event_tags, experiment_variation_map)
|
214
203
|
@logger.log(Logger::INFO,
|
215
204
|
'Dispatching conversion event to URL %s with params %s.' % [conversion_event.url,
|
216
205
|
conversion_event.params])
|
@@ -223,7 +212,35 @@ module Optimizely
|
|
223
212
|
|
224
213
|
private
|
225
214
|
|
226
|
-
def
|
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)
|
227
244
|
# Validates preconditions for bucketing a user.
|
228
245
|
#
|
229
246
|
# experiment_key - String key for an experiment.
|
@@ -232,18 +249,29 @@ module Optimizely
|
|
232
249
|
#
|
233
250
|
# Returns boolean representing whether all preconditions are valid.
|
234
251
|
|
252
|
+
return false unless user_inputs_valid?(attributes, event_tags)
|
253
|
+
|
235
254
|
unless @config.experiment_running?(experiment_key)
|
236
255
|
@logger.log(Logger::INFO, "Experiment '#{experiment_key}' is not running.")
|
237
256
|
return false
|
238
257
|
end
|
239
258
|
|
240
|
-
|
241
|
-
|
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
|
242
272
|
end
|
243
273
|
|
244
|
-
|
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)
|
247
275
|
return false
|
248
276
|
end
|
249
277
|
|
@@ -268,7 +296,7 @@ module Optimizely
|
|
268
296
|
true
|
269
297
|
end
|
270
298
|
|
271
|
-
def
|
299
|
+
def validate_instantiation_options(datafile, skip_json_validation)
|
272
300
|
unless skip_json_validation
|
273
301
|
raise InvalidInputError.new('datafile') unless Helpers::Validator.datafile_valid?(datafile)
|
274
302
|
end
|
data/lib/optimizely/bucketer.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
#
|
2
|
-
# Copyright 2016, Optimizely and contributors
|
2
|
+
# Copyright 2016-2017, Optimizely and contributors
|
3
3
|
#
|
4
4
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
5
|
# you may not use this file except in compliance with the License.
|
@@ -43,23 +43,6 @@ module Optimizely
|
|
43
43
|
#
|
44
44
|
# Returns String variation ID in which visitor with ID user_id has been placed. Nil if no variation.
|
45
45
|
|
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
|
-
|
63
46
|
# check if experiment is in a group; if so, check if user is bucketed into specified experiment
|
64
47
|
experiment_id = @config.get_experiment_id(experiment_key)
|
65
48
|
group_id = @config.get_experiment_group_id(experiment_key)
|
@@ -118,6 +101,36 @@ module Optimizely
|
|
118
101
|
nil
|
119
102
|
end
|
120
103
|
|
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
|
+
|
121
134
|
private
|
122
135
|
|
123
136
|
def find_bucket(bucket_value, traffic_allocations)
|
@@ -42,12 +42,10 @@ module Optimizely
|
|
42
42
|
|
43
43
|
class BaseEventBuilder
|
44
44
|
attr_reader :config
|
45
|
-
attr_reader :bucketer
|
46
45
|
attr_accessor :params
|
47
46
|
|
48
|
-
def initialize(config
|
47
|
+
def initialize(config)
|
49
48
|
@config = config
|
50
|
-
@bucketer = bucketer
|
51
49
|
@params = {}
|
52
50
|
end
|
53
51
|
|
@@ -90,14 +88,14 @@ module Optimizely
|
|
90
88
|
Event.new(:post, IMPRESSION_EVENT_ENDPOINT, @params, POST_HEADERS)
|
91
89
|
end
|
92
90
|
|
93
|
-
def create_conversion_event(event_key, user_id, attributes, event_tags,
|
91
|
+
def create_conversion_event(event_key, user_id, attributes, event_tags, experiment_variation_map)
|
94
92
|
# Create conversion Event to be sent to the logging endpoint.
|
95
93
|
#
|
96
94
|
# event_key - Event key representing the event which needs to be recorded.
|
97
95
|
# user_id - ID for user.
|
98
96
|
# attributes - Hash representing user attributes and values which need to be recorded.
|
99
97
|
# event_tags - Hash representing metadata associated with the event.
|
100
|
-
#
|
98
|
+
# experiment_variation_map - Map of experiment ID to the ID of the variation that the user is bucketed into.
|
101
99
|
#
|
102
100
|
# Returns event hash encapsulating the conversion event.
|
103
101
|
|
@@ -105,7 +103,7 @@ module Optimizely
|
|
105
103
|
add_common_params(user_id, attributes)
|
106
104
|
add_conversion_event(event_key)
|
107
105
|
add_event_tags(event_tags)
|
108
|
-
add_layer_states(
|
106
|
+
add_layer_states(experiment_variation_map)
|
109
107
|
Event.new(:post, CONVERSION_EVENT_ENDPOINT, @params, POST_HEADERS)
|
110
108
|
end
|
111
109
|
|
@@ -206,14 +204,16 @@ module Optimizely
|
|
206
204
|
@params['eventName'] = event_name
|
207
205
|
end
|
208
206
|
|
209
|
-
def add_layer_states(
|
207
|
+
def add_layer_states(experiments_map)
|
208
|
+
# Add layer states information to the event.
|
209
|
+
#
|
210
|
+
# experiments_map - Hash with experiment ID as a key and variation ID as a value.
|
211
|
+
|
210
212
|
@params['layerStates'] = []
|
211
213
|
|
212
|
-
|
213
|
-
variation_id = @bucketer.bucket(experiment_key, user_id)
|
214
|
-
experiment_id = @config.experiment_key_map[experiment_key]['id']
|
214
|
+
experiments_map.each do |experiment_id, variation_id|
|
215
215
|
layer_state = {
|
216
|
-
'layerId' => @config.
|
216
|
+
'layerId' => @config.experiment_id_map[experiment_id]['layerId'],
|
217
217
|
'decision' => {
|
218
218
|
'variationId' => variation_id,
|
219
219
|
'experimentId' => experiment_id,
|
@@ -234,162 +234,4 @@ module Optimizely
|
|
234
234
|
@params['timestamp'] = (Time.now.to_f * 1000).to_i
|
235
235
|
end
|
236
236
|
end
|
237
|
-
|
238
|
-
class EventBuilderV1 < BaseEventBuilder
|
239
|
-
# Class which encapsulates methods to build events for tracking impressions and conversions.
|
240
|
-
|
241
|
-
# Attribute mapping format
|
242
|
-
ATTRIBUTE_PARAM_FORMAT = '%{segment_prefix}%{segment_id}'
|
243
|
-
|
244
|
-
# Experiment mapping format
|
245
|
-
EXPERIMENT_PARAM_FORMAT = '%{experiment_prefix}%{experiment_id}'
|
246
|
-
|
247
|
-
# Event endpoint path
|
248
|
-
OFFLINE_API_PATH = 'https://%{project_id}.log.optimizely.com/event'
|
249
|
-
|
250
|
-
def create_impression_event(experiment_key, variation_id, user_id, attributes)
|
251
|
-
# Create conversion Event to be sent to the logging endpoint.
|
252
|
-
#
|
253
|
-
# experiment_key - Experiment for which impression needs to be recorded.
|
254
|
-
# variation_id - ID for variation which would be presented to user.
|
255
|
-
# user_id - ID for user.
|
256
|
-
# attributes - Hash representing user attributes and values which need to be recorded.
|
257
|
-
#
|
258
|
-
# Returns event hash encapsulating the impression event.
|
259
|
-
|
260
|
-
@params = {}
|
261
|
-
add_common_params(user_id, attributes)
|
262
|
-
add_impression_goal(experiment_key)
|
263
|
-
add_experiment(experiment_key, variation_id)
|
264
|
-
Event.new(:get, sprintf(OFFLINE_API_PATH, project_id: @params[Params::PROJECT_ID]), @params, {})
|
265
|
-
end
|
266
|
-
|
267
|
-
def create_conversion_event(event_key, user_id, attributes, event_tags, experiment_keys)
|
268
|
-
# Create conversion Event to be sent to the logging endpoint.
|
269
|
-
#
|
270
|
-
# event_key - Goal key representing the event which needs to be recorded.
|
271
|
-
# user_id - ID for user.
|
272
|
-
# attributes - Hash representing user attributes and values which need to be recorded.
|
273
|
-
# event_tags - Hash representing metadata associated with the event.
|
274
|
-
# experiment_keys - Array of valid experiment keys for the goal
|
275
|
-
#
|
276
|
-
# Returns event hash encapsulating the conversion event.
|
277
|
-
|
278
|
-
@params = {}
|
279
|
-
|
280
|
-
event_value = Helpers::EventTagUtils.get_revenue_value(event_tags)
|
281
|
-
|
282
|
-
add_common_params(user_id, attributes)
|
283
|
-
add_conversion_goal(event_key, event_value)
|
284
|
-
add_experiment_variation_params(user_id, experiment_keys)
|
285
|
-
Event.new(:get, sprintf(OFFLINE_API_PATH, project_id: @params[Params::PROJECT_ID]), @params, {})
|
286
|
-
end
|
287
|
-
|
288
|
-
private
|
289
|
-
|
290
|
-
def add_project_id
|
291
|
-
# Add project ID to the event.
|
292
|
-
|
293
|
-
@params[Params::PROJECT_ID] = @config.project_id
|
294
|
-
end
|
295
|
-
|
296
|
-
def add_account_id
|
297
|
-
# Add account ID to the event.
|
298
|
-
|
299
|
-
@params[Params::ACCOUNT_ID] = @config.account_id
|
300
|
-
end
|
301
|
-
|
302
|
-
def add_user_id(user_id)
|
303
|
-
# Add user ID to the event.
|
304
|
-
|
305
|
-
@params[Params::END_USER_ID] = user_id
|
306
|
-
end
|
307
|
-
|
308
|
-
def add_attributes(attributes)
|
309
|
-
# Add attribute(s) information to the event.
|
310
|
-
#
|
311
|
-
# attributes - Hash representing user attributes and values which need to be recorded.
|
312
|
-
|
313
|
-
return if attributes.nil?
|
314
|
-
|
315
|
-
attributes.keys.each do |attribute_key|
|
316
|
-
attribute_value = attributes[attribute_key]
|
317
|
-
next unless attribute_value
|
318
|
-
|
319
|
-
# Skip attributes not in the datafile
|
320
|
-
segment_id = @config.get_segment_id(attribute_key)
|
321
|
-
next unless segment_id
|
322
|
-
segment_param = sprintf(ATTRIBUTE_PARAM_FORMAT,
|
323
|
-
segment_prefix: Params::SEGMENT_PREFIX, segment_id: segment_id)
|
324
|
-
params[segment_param] = attribute_value
|
325
|
-
end
|
326
|
-
end
|
327
|
-
|
328
|
-
def add_source
|
329
|
-
# Add source information to the event.
|
330
|
-
|
331
|
-
@params[Params::SOURCE] = sprintf('ruby-sdk-%{version}', version: VERSION)
|
332
|
-
end
|
333
|
-
|
334
|
-
def add_time
|
335
|
-
# Add time information to the event.
|
336
|
-
|
337
|
-
@params[Params::TIME] = Time.now.strftime('%s').to_i
|
338
|
-
end
|
339
|
-
|
340
|
-
def add_impression_goal(experiment_key)
|
341
|
-
# Add impression goal information to the event.
|
342
|
-
#
|
343
|
-
# experiment_key - Experiment which is being activated.
|
344
|
-
|
345
|
-
# For tracking impressions, goal ID is set equal to experiment ID of experiment being activated.
|
346
|
-
@params[Params::GOAL_ID] = @config.get_experiment_id(experiment_key)
|
347
|
-
@params[Params::GOAL_NAME] = 'visitor-event'
|
348
|
-
end
|
349
|
-
|
350
|
-
def add_experiment(experiment_key, variation_id)
|
351
|
-
# Add experiment to variation mapping to the impression event.
|
352
|
-
#
|
353
|
-
# experiment_key - Experiment which is being activated.
|
354
|
-
# variation_id - ID for variation which would be presented to user.
|
355
|
-
|
356
|
-
experiment_id = @config.get_experiment_id(experiment_key)
|
357
|
-
experiment_param = sprintf(EXPERIMENT_PARAM_FORMAT,
|
358
|
-
experiment_prefix: Params::EXPERIMENT_PREFIX, experiment_id: experiment_id)
|
359
|
-
@params[experiment_param] = variation_id
|
360
|
-
end
|
361
|
-
|
362
|
-
def add_experiment_variation_params(user_id, experiment_keys)
|
363
|
-
# Maps experiment and corresponding variation as parameters to be used in the event tracking call.
|
364
|
-
#
|
365
|
-
# user_id - ID for user.
|
366
|
-
# experiment_keys - Array of valid experiment keys for the goal
|
367
|
-
|
368
|
-
experiment_keys.each do |experiment_key|
|
369
|
-
variation_id = @bucketer.bucket(experiment_key, user_id)
|
370
|
-
experiment_id = @config.experiment_key_map[experiment_key]['id']
|
371
|
-
experiment_param = sprintf(EXPERIMENT_PARAM_FORMAT,
|
372
|
-
experiment_prefix: Params::EXPERIMENT_PREFIX, experiment_id: experiment_id)
|
373
|
-
@params[experiment_param] = variation_id
|
374
|
-
end
|
375
|
-
end
|
376
|
-
|
377
|
-
def add_conversion_goal(event_key, event_value)
|
378
|
-
# Add conversion goal information to the event.
|
379
|
-
#
|
380
|
-
# event_key - Goal key representing the event which needs to be recorded.
|
381
|
-
# event_value - Value associated with the event. Can be used to represent revenue in cents.
|
382
|
-
|
383
|
-
goal_id = @config.event_key_map[event_key]['id']
|
384
|
-
event_ids = goal_id
|
385
|
-
|
386
|
-
if event_value
|
387
|
-
event_ids = sprintf('%{goal_id},%{revenue_id}', goal_id: goal_id, revenue_id: @config.get_revenue_goal_id)
|
388
|
-
@params[Params::EVENT_VALUE] = event_value
|
389
|
-
end
|
390
|
-
|
391
|
-
@params[Params::GOAL_ID] = event_ids
|
392
|
-
@params[Params::GOAL_NAME] = event_key
|
393
|
-
end
|
394
|
-
end
|
395
237
|
end
|
@@ -1,5 +1,5 @@
|
|
1
1
|
#
|
2
|
-
# Copyright 2016, Optimizely and contributors
|
2
|
+
# Copyright 2016-2017, Optimizely and contributors
|
3
3
|
#
|
4
4
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
5
|
# you may not use this file except in compliance with the License.
|
@@ -83,7 +83,7 @@ module Optimizely
|
|
83
83
|
class InvalidDatafileVersionError < Error
|
84
84
|
# Raised when a datafile with an unsupported version is provided
|
85
85
|
|
86
|
-
def initialize(msg = 'Provided datafile is an unsupported version.')
|
86
|
+
def initialize(msg = 'Provided datafile is an unsupported version. Please use SDK version 1.1.2 or earlier for datafile version 1.')
|
87
87
|
super
|
88
88
|
end
|
89
89
|
end
|
@@ -1,5 +1,5 @@
|
|
1
1
|
#
|
2
|
-
# Copyright 2016, Optimizely and contributors
|
2
|
+
# Copyright 2016-2017, Optimizely and contributors
|
3
3
|
#
|
4
4
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
5
|
# you may not use this file except in compliance with the License.
|
@@ -16,283 +16,6 @@
|
|
16
16
|
module Optimizely
|
17
17
|
module Helpers
|
18
18
|
module Constants
|
19
|
-
JSON_SCHEMA_V1 = {
|
20
|
-
'type' => 'object',
|
21
|
-
'properties' => {
|
22
|
-
'projectId' => {
|
23
|
-
'type' => 'string'
|
24
|
-
},
|
25
|
-
'accountId' => {
|
26
|
-
'type' => 'string'
|
27
|
-
},
|
28
|
-
'groups' => {
|
29
|
-
'type' => 'array',
|
30
|
-
'items' => {
|
31
|
-
'type' => 'object',
|
32
|
-
'properties' => {
|
33
|
-
'id' => {
|
34
|
-
'type' => 'string'
|
35
|
-
},
|
36
|
-
'policy' => {
|
37
|
-
'type' => 'string'
|
38
|
-
},
|
39
|
-
'trafficAllocation' => {
|
40
|
-
'type' => 'array',
|
41
|
-
'items' => {
|
42
|
-
'type' => 'object',
|
43
|
-
'properties' => {
|
44
|
-
'entityId' => {
|
45
|
-
'type' => 'string'
|
46
|
-
},
|
47
|
-
'endOfRange' => {
|
48
|
-
'type' => 'integer'
|
49
|
-
}
|
50
|
-
},
|
51
|
-
'required' => [
|
52
|
-
'entityId',
|
53
|
-
'endOfRange'
|
54
|
-
]
|
55
|
-
}
|
56
|
-
},
|
57
|
-
'experiments' => {
|
58
|
-
'type' => 'array',
|
59
|
-
'items' => {
|
60
|
-
'type' => 'object',
|
61
|
-
'properties' => {
|
62
|
-
'id' => {
|
63
|
-
'type' => 'string'
|
64
|
-
},
|
65
|
-
'key' => {
|
66
|
-
'type' => 'string'
|
67
|
-
},
|
68
|
-
'status' => {
|
69
|
-
'type' => 'string'
|
70
|
-
},
|
71
|
-
'variations' => {
|
72
|
-
'type' => 'array',
|
73
|
-
'items' => {
|
74
|
-
'type' => 'object',
|
75
|
-
'properties' => {
|
76
|
-
'id' => {
|
77
|
-
'type' => 'string'
|
78
|
-
},
|
79
|
-
'key' => {
|
80
|
-
'type' => 'string'
|
81
|
-
}
|
82
|
-
},
|
83
|
-
'required' => [
|
84
|
-
'id',
|
85
|
-
'key'
|
86
|
-
]
|
87
|
-
}
|
88
|
-
},
|
89
|
-
'trafficAllocation' => {
|
90
|
-
'type' => 'array',
|
91
|
-
'items' => {
|
92
|
-
'type' => 'object',
|
93
|
-
'properties' => {
|
94
|
-
'entityId' => {
|
95
|
-
'type' => 'string'
|
96
|
-
},
|
97
|
-
'endOfRange' => {
|
98
|
-
'type' => 'integer'
|
99
|
-
}
|
100
|
-
},
|
101
|
-
'required' => [
|
102
|
-
'entityId',
|
103
|
-
'endOfRange'
|
104
|
-
]
|
105
|
-
}
|
106
|
-
},
|
107
|
-
'audienceIds' => {
|
108
|
-
'type' => 'array',
|
109
|
-
'items' => {
|
110
|
-
'type' => 'string'
|
111
|
-
}
|
112
|
-
},
|
113
|
-
'forcedVariations' => {
|
114
|
-
'type' => 'object'
|
115
|
-
}
|
116
|
-
},
|
117
|
-
'required' => [
|
118
|
-
'id',
|
119
|
-
'key',
|
120
|
-
'status',
|
121
|
-
'variations',
|
122
|
-
'trafficAllocation',
|
123
|
-
'audienceIds',
|
124
|
-
'forcedVariations'
|
125
|
-
]
|
126
|
-
}
|
127
|
-
}
|
128
|
-
},
|
129
|
-
'required' => [
|
130
|
-
'id',
|
131
|
-
'policy',
|
132
|
-
'trafficAllocation',
|
133
|
-
'experiments'
|
134
|
-
]
|
135
|
-
}
|
136
|
-
},
|
137
|
-
'experiments' => {
|
138
|
-
'type' => 'array',
|
139
|
-
'items' => {
|
140
|
-
'type' => 'object',
|
141
|
-
'properties' => {
|
142
|
-
'id' => {
|
143
|
-
'type' => 'string'
|
144
|
-
},
|
145
|
-
'key' => {
|
146
|
-
'type' => 'string'
|
147
|
-
},
|
148
|
-
'status' => {
|
149
|
-
'type' => 'string'
|
150
|
-
},
|
151
|
-
'variations' => {
|
152
|
-
'type' => 'array',
|
153
|
-
'items' => {
|
154
|
-
'type' => 'object',
|
155
|
-
'properties' => {
|
156
|
-
'id' => {
|
157
|
-
'type' => 'string'
|
158
|
-
},
|
159
|
-
'key' => {
|
160
|
-
'type' => 'string'
|
161
|
-
}
|
162
|
-
},
|
163
|
-
'required' => [
|
164
|
-
'id',
|
165
|
-
'key'
|
166
|
-
]
|
167
|
-
}
|
168
|
-
},
|
169
|
-
'trafficAllocation' => {
|
170
|
-
'type' => 'array',
|
171
|
-
'items' => {
|
172
|
-
'type' => 'object',
|
173
|
-
'properties' => {
|
174
|
-
'entityId' => {
|
175
|
-
'type' => 'string'
|
176
|
-
},
|
177
|
-
'endOfRange' => {
|
178
|
-
'type' => 'integer'
|
179
|
-
}
|
180
|
-
},
|
181
|
-
'required' => [
|
182
|
-
'entityId',
|
183
|
-
'endOfRange'
|
184
|
-
]
|
185
|
-
}
|
186
|
-
},
|
187
|
-
'audienceIds' => {
|
188
|
-
'type' => 'array',
|
189
|
-
'items' => {
|
190
|
-
'type' => 'string'
|
191
|
-
}
|
192
|
-
},
|
193
|
-
'forcedVariations' => {
|
194
|
-
'type' => 'object'
|
195
|
-
}
|
196
|
-
},
|
197
|
-
'required' => [
|
198
|
-
'id',
|
199
|
-
'key',
|
200
|
-
'variations',
|
201
|
-
'trafficAllocation',
|
202
|
-
'audienceIds',
|
203
|
-
'forcedVariations',
|
204
|
-
'status',
|
205
|
-
]
|
206
|
-
}
|
207
|
-
},
|
208
|
-
'events' => {
|
209
|
-
'type' => 'array',
|
210
|
-
'items' => {
|
211
|
-
'type' => 'object',
|
212
|
-
'properties' => {
|
213
|
-
'key' => {
|
214
|
-
'type' => 'string'
|
215
|
-
},
|
216
|
-
'experimentIds' => {
|
217
|
-
'type' => 'array',
|
218
|
-
'items' => {
|
219
|
-
'type' => 'string'
|
220
|
-
}
|
221
|
-
},
|
222
|
-
'id' => {
|
223
|
-
'type' => 'string'
|
224
|
-
}
|
225
|
-
},
|
226
|
-
'required' => [
|
227
|
-
'key',
|
228
|
-
'experimentIds',
|
229
|
-
'id'
|
230
|
-
]
|
231
|
-
}
|
232
|
-
},
|
233
|
-
'audiences' => {
|
234
|
-
'type' => 'array',
|
235
|
-
'items' => {
|
236
|
-
'type' => 'object',
|
237
|
-
'properties' => {
|
238
|
-
'id' => {
|
239
|
-
'type' => 'string'
|
240
|
-
},
|
241
|
-
'name' => {
|
242
|
-
'type' => 'string'
|
243
|
-
},
|
244
|
-
'conditions' => {
|
245
|
-
'type' => 'string'
|
246
|
-
}
|
247
|
-
},
|
248
|
-
'required' => [
|
249
|
-
'id',
|
250
|
-
'name',
|
251
|
-
'conditions'
|
252
|
-
]
|
253
|
-
}
|
254
|
-
},
|
255
|
-
'dimensions' => {
|
256
|
-
'type' => 'array',
|
257
|
-
'items' => {
|
258
|
-
'type' => 'object',
|
259
|
-
'properties' => {
|
260
|
-
'id' => {
|
261
|
-
'type' => 'string'
|
262
|
-
},
|
263
|
-
'key' => {
|
264
|
-
'type' => 'string'
|
265
|
-
},
|
266
|
-
'segmentId' => {
|
267
|
-
'type' => 'string'
|
268
|
-
}
|
269
|
-
},
|
270
|
-
'required' => [
|
271
|
-
'id',
|
272
|
-
'key',
|
273
|
-
'segmentId'
|
274
|
-
]
|
275
|
-
}
|
276
|
-
},
|
277
|
-
'version' => {
|
278
|
-
'type' => 'string'
|
279
|
-
},
|
280
|
-
'revision' => {
|
281
|
-
'type' => 'string'
|
282
|
-
}
|
283
|
-
},
|
284
|
-
'required' => [
|
285
|
-
'projectId',
|
286
|
-
'accountId',
|
287
|
-
'experiments',
|
288
|
-
'events',
|
289
|
-
'groups',
|
290
|
-
'audiences',
|
291
|
-
'dimensions',
|
292
|
-
'version',
|
293
|
-
'revision'
|
294
|
-
]
|
295
|
-
}
|
296
19
|
|
297
20
|
JSON_SCHEMA_V2 = {
|
298
21
|
'type' => 'object',
|
@@ -1,5 +1,5 @@
|
|
1
1
|
#
|
2
|
-
# Copyright 2016, Optimizely and contributors
|
2
|
+
# Copyright 2016-2017, Optimizely and contributors
|
3
3
|
#
|
4
4
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
5
|
# you may not use this file except in compliance with the License.
|
@@ -55,13 +55,7 @@ module Optimizely
|
|
55
55
|
return false
|
56
56
|
end
|
57
57
|
|
58
|
-
|
59
|
-
|
60
|
-
if version == Optimizely::V1_CONFIG_VERSION
|
61
|
-
JSON::Validator.validate(Helpers::Constants::JSON_SCHEMA_V1, datafile)
|
62
|
-
else
|
63
|
-
JSON::Validator.validate(Helpers::Constants::JSON_SCHEMA_V2, datafile)
|
64
|
-
end
|
58
|
+
JSON::Validator.validate(Helpers::Constants::JSON_SCHEMA_V2, datafile)
|
65
59
|
end
|
66
60
|
|
67
61
|
def error_handler_valid?(error_handler)
|
@@ -1,5 +1,5 @@
|
|
1
1
|
#
|
2
|
-
# Copyright 2016, Optimizely and contributors
|
2
|
+
# Copyright 2016-2017, Optimizely and contributors
|
3
3
|
#
|
4
4
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
5
|
# you may not use this file except in compliance with the License.
|
@@ -20,6 +20,9 @@ module Optimizely
|
|
20
20
|
V1_CONFIG_VERSION = '1'
|
21
21
|
V2_CONFIG_VERSION = '2'
|
22
22
|
|
23
|
+
SUPPORTED_VERSIONS = [V2_CONFIG_VERSION]
|
24
|
+
UNSUPPORTED_VERSIONS = [V1_CONFIG_VERSION]
|
25
|
+
|
23
26
|
class ProjectConfig
|
24
27
|
# Representation of the Optimizely project config.
|
25
28
|
|
@@ -32,6 +35,7 @@ module Optimizely
|
|
32
35
|
attr_reader :error_handler
|
33
36
|
attr_reader :logger
|
34
37
|
|
38
|
+
attr_reader :parsing_succeeded
|
35
39
|
attr_reader :version
|
36
40
|
attr_reader :account_id
|
37
41
|
attr_reader :project_id
|
@@ -59,16 +63,18 @@ module Optimizely
|
|
59
63
|
|
60
64
|
config = JSON.load(datafile)
|
61
65
|
|
66
|
+
@parsing_succeeded = false
|
62
67
|
@error_handler = error_handler
|
63
68
|
@logger = logger
|
64
69
|
@version = config['version']
|
70
|
+
|
71
|
+
if UNSUPPORTED_VERSIONS.include?(@version)
|
72
|
+
return
|
73
|
+
end
|
74
|
+
|
65
75
|
@account_id = config['accountId']
|
66
76
|
@project_id = config['projectId']
|
67
|
-
|
68
|
-
@attributes = config['dimensions']
|
69
|
-
else
|
70
|
-
@attributes = config['attributes']
|
71
|
-
end
|
77
|
+
@attributes = config['attributes']
|
72
78
|
@events = config['events']
|
73
79
|
@experiments = config['experiments']
|
74
80
|
@revision = config['revision']
|
@@ -95,6 +101,7 @@ module Optimizely
|
|
95
101
|
@variation_id_map[key] = generate_key_map(variations, 'id')
|
96
102
|
@variation_key_map[key] = generate_key_map(variations, 'key')
|
97
103
|
end
|
104
|
+
@parsing_succeeded = true
|
98
105
|
end
|
99
106
|
|
100
107
|
def experiment_running?(experiment_key)
|
@@ -124,36 +131,30 @@ module Optimizely
|
|
124
131
|
nil
|
125
132
|
end
|
126
133
|
|
127
|
-
def
|
128
|
-
# Retrieves
|
134
|
+
def get_experiment_key(experiment_id)
|
135
|
+
# Retrieves experiment key for a given ID.
|
129
136
|
#
|
130
|
-
#
|
131
|
-
|
132
|
-
goal_keys = @event_key_map.keys
|
133
|
-
goal_keys.delete(REVENUE_GOAL_KEY) if goal_keys.include?(REVENUE_GOAL_KEY)
|
134
|
-
goal_keys
|
135
|
-
end
|
136
|
-
|
137
|
-
def get_revenue_goal_id
|
138
|
-
# Get ID of the revenue goal for the project
|
137
|
+
# experiment_id - String ID representing the experiment.
|
139
138
|
#
|
140
|
-
# Returns
|
139
|
+
# Returns String key.
|
141
140
|
|
142
|
-
|
143
|
-
return
|
141
|
+
experiment = @experiment_id_map[experiment_id]
|
142
|
+
return experiment['key'] unless experiment.nil?
|
143
|
+
@logger.log Logger::ERROR, "Experiment id '#{experiment_id}' is not in datafile."
|
144
|
+
@error_handler.handle_error InvalidExperimentError
|
144
145
|
nil
|
145
146
|
end
|
146
147
|
|
147
|
-
def
|
148
|
-
# Get experiment IDs for the provided
|
148
|
+
def get_experiment_ids_for_event(event_key)
|
149
|
+
# Get experiment IDs for the provided event key.
|
149
150
|
#
|
150
|
-
#
|
151
|
+
# event_key - Event key for which experiment IDs are to be retrieved.
|
151
152
|
#
|
152
|
-
# Returns array of all experiment IDs for the
|
153
|
+
# Returns array of all experiment IDs for the event.
|
153
154
|
|
154
|
-
|
155
|
-
return
|
156
|
-
@logger.log Logger::ERROR, "Event '#{
|
155
|
+
event = @event_key_map[event_key]
|
156
|
+
return event['experimentIds'] if event
|
157
|
+
@logger.log Logger::ERROR, "Event '#{event_key}' is not in datafile."
|
157
158
|
@error_handler.handle_error InvalidEventError
|
158
159
|
[]
|
159
160
|
end
|
@@ -272,14 +273,6 @@ module Optimizely
|
|
272
273
|
nil
|
273
274
|
end
|
274
275
|
|
275
|
-
def get_segment_id(attribute_key)
|
276
|
-
attribute = @attribute_key_map[attribute_key]
|
277
|
-
return attribute['segmentId'] if attribute
|
278
|
-
@logger.log Logger::ERROR, "Attribute key '#{attribute_key}' is not in datafile."
|
279
|
-
@error_handler.handle_error InvalidAttributeError
|
280
|
-
nil
|
281
|
-
end
|
282
|
-
|
283
276
|
def user_in_forced_variation?(experiment_key, user_id)
|
284
277
|
# Determines if a given user is in a forced variation
|
285
278
|
#
|
@@ -293,6 +286,14 @@ module Optimizely
|
|
293
286
|
false
|
294
287
|
end
|
295
288
|
|
289
|
+
def parsing_succeeded?
|
290
|
+
# Helper method to determine if parsing the datafile was successful.
|
291
|
+
#
|
292
|
+
# Returns Boolean depending on whether parsing the datafile succeeded or not.
|
293
|
+
|
294
|
+
@parsing_succeeded
|
295
|
+
end
|
296
|
+
|
296
297
|
private
|
297
298
|
|
298
299
|
def generate_key_map(array, key)
|
data/lib/optimizely/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: optimizely-sdk
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andrew Delikat
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2017-
|
13
|
+
date: 2017-05-05 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: bundler
|
@@ -153,7 +153,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
153
153
|
version: '0'
|
154
154
|
requirements: []
|
155
155
|
rubyforge_project:
|
156
|
-
rubygems_version: 2.
|
156
|
+
rubygems_version: 2.6.11
|
157
157
|
signing_key:
|
158
158
|
specification_version: 4
|
159
159
|
summary: Ruby SDK for Optimizely's testing framework
|