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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c74599de62f5c8f5f5dee2484384c5dc7c3de7e8
4
- data.tar.gz: 53319f3eb1ab45bab9b609f8e901d5a78ecfb009
3
+ metadata.gz: c8eff682f4c4c99808f246f2828a4e34e276e54b
4
+ data.tar.gz: 1c4de8ae37ce421199a1d66ea67c5e9ba78e1c41
5
5
  SHA512:
6
- metadata.gz: b90f7312495293123be649f29d14fdcf6f4497e3094b160ae7d424bd1e3807a0775971d47473e0cf9a835b48cd7a65ac9b7a22e10670e6c72ae82751e8e1043a
7
- data.tar.gz: 478ce61817912292b29d2d9b08e6de617e30f400ef8d5e44f707036692f7d00acb96d409bc52dd0a6d323d15ba6d0c5cd056baab7ad93e439574bec8058b7709
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
- validate_inputs(datafile, skip_json_validation)
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
- 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?
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
- 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)
115
101
 
116
- if not variation_id
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
- @config.get_variation_key_from_id(experiment_key, variation_id)
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
- if attributes && !attributes_valid?(attributes)
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
- 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}'.")
158
152
  return nil
159
153
  end
160
154
 
161
155
  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
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 - Goal key representing the event which needs to be recorded.
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 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)
188
184
 
189
- experiment_ids = @config.get_experiment_ids_for_goal(event_key)
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
- 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)
205
194
 
206
195
  # Don't track events without valid experiments attached
207
- if valid_experiment_keys.empty?
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, valid_experiment_keys)
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 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)
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
- 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
242
272
  end
243
273
 
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)
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 validate_inputs(datafile, skip_json_validation)
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
@@ -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, bucketer)
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, experiment_keys)
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
- # experiment_keys - Array of valid experiment keys for the event
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(user_id, experiment_keys)
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(user_id, experiment_keys)
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
- experiment_keys.each do |experiment_key|
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.experiment_key_map[experiment_key]['layerId'],
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
- version = datafile['version']
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
- if @version == V1_CONFIG_VERSION
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 get_goal_keys
128
- # Retrieves all goals in the project except 'Total Revenue'
134
+ def get_experiment_key(experiment_id)
135
+ # Retrieves experiment key for a given ID.
129
136
  #
130
- # Returns array of all goal keys except 'Total Revenue'
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 revenue goal ID
139
+ # Returns String key.
141
140
 
142
- revenue_goal = @event_key_map[REVENUE_GOAL_KEY]
143
- return revenue_goal['id'] if revenue_goal
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 get_experiment_ids_for_goal(goal_key)
148
- # Get experiment IDs for the provided goal key.
148
+ def get_experiment_ids_for_event(event_key)
149
+ # Get experiment IDs for the provided event key.
149
150
  #
150
- # goal_key - Goal key for which experiment IDs are to be retrieved.
151
+ # event_key - Event key for which experiment IDs are to be retrieved.
151
152
  #
152
- # Returns array of all experiment IDs for the goal.
153
+ # Returns array of all experiment IDs for the event.
153
154
 
154
- goal = @event_key_map[goal_key]
155
- return goal['experimentIds'] if goal
156
- @logger.log Logger::ERROR, "Event '#{goal_key}' is not in datafile."
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)
@@ -14,5 +14,5 @@
14
14
  # limitations under the License.
15
15
  #
16
16
  module Optimizely
17
- VERSION = '1.1.2'.freeze
17
+ VERSION = '1.2.0'.freeze
18
18
  end
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.1.2
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-03-22 00:00:00.000000000 Z
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.5.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