optimizely-sdk 1.1.2 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|