Class: Optimizely::Project

Inherits:
Object
  • Object
show all
Defined in:
lib/optimizely.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(datafile, event_dispatcher = nil, logger = nil, error_handler = nil, skip_json_validation = false, user_profile_service = nil) ⇒ Project

Returns a new instance of Project.



45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
# File 'lib/optimizely.rb', line 45

def initialize(datafile, event_dispatcher = nil, logger = nil, error_handler = nil, skip_json_validation = false,  = nil)
  # Constructor for Projects.
  #
  # datafile - JSON string representing the project.
  # event_dispatcher - Provides a dispatch_event method which if given a URL and params sends a request to it.
  # logger - Optional component which provides a log method to log messages. By default nothing would be logged.
  # error_handler - Optional component which provides a handle_error method to handle exceptions.
  #                 By default all exceptions will be suppressed.
  # user_profile_service - Optional component which provides methods to store and retreive user profiles.
  # skip_json_validation - Optional boolean param to skip JSON schema validation of the provided datafile.

  @is_valid = true
  @logger = logger || NoOpLogger.new
  @error_handler = error_handler || NoOpErrorHandler.new
  @event_dispatcher = event_dispatcher || EventDispatcher.new
  @user_profile_service = 

  begin
    validate_instantiation_options(datafile, skip_json_validation)
  rescue InvalidInputError => e
    @is_valid = false
    logger = SimpleLogger.new
    logger.log(Logger::ERROR, e.message)
    return
  end

  begin
    @config = ProjectConfig.new(datafile, @logger, @error_handler)
  rescue
    @is_valid = false
    logger = SimpleLogger.new
    logger.log(Logger::ERROR, InvalidInputError.new('datafile').message)
    return
  end

  unless @config.parsing_succeeded?
    @is_valid = false
    logger = SimpleLogger.new
    logger.log(Logger::ERROR, InvalidDatafileVersionError.new.message)
    return
  end

  @decision_service = DecisionService.new(@config, @user_profile_service)
  @event_builder = EventBuilder.new(@config, logger)
  @notification_center = NotificationCenter.new(@logger, @error_handler)
end

Instance Attribute Details

#configObject (readonly)

Returns the value of attribute config.



37
38
39
# File 'lib/optimizely.rb', line 37

def config
  @config
end

#decision_serviceObject (readonly)

Returns the value of attribute decision_service.



38
39
40
# File 'lib/optimizely.rb', line 38

def decision_service
  @decision_service
end

#error_handlerObject (readonly)

Returns the value of attribute error_handler.



39
40
41
# File 'lib/optimizely.rb', line 39

def error_handler
  @error_handler
end

#event_builderObject (readonly)

Returns the value of attribute event_builder.



40
41
42
# File 'lib/optimizely.rb', line 40

def event_builder
  @event_builder
end

#event_dispatcherObject (readonly)

Returns the value of attribute event_dispatcher.



41
42
43
# File 'lib/optimizely.rb', line 41

def event_dispatcher
  @event_dispatcher
end

#is_validObject (readonly)

Boolean representing if the instance represents a usable Optimizely Project



35
36
37
# File 'lib/optimizely.rb', line 35

def is_valid
  @is_valid
end

#loggerObject (readonly)

Returns the value of attribute logger.



42
43
44
# File 'lib/optimizely.rb', line 42

def logger
  @logger
end

#notification_centerObject (readonly)

Returns the value of attribute notification_center.



43
44
45
# File 'lib/optimizely.rb', line 43

def notification_center
  @notification_center
end

Instance Method Details

#activate(experiment_key, user_id, attributes = nil) ⇒ Object



92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
# File 'lib/optimizely.rb', line 92

def activate(experiment_key, user_id, attributes = nil)
  # Buckets visitor and sends impression event to Optimizely.
  #
  # experiment_key - Experiment which needs to be activated.
  # user_id - String ID for user.
  # attributes - Hash representing user attributes and values to be recorded.
  #
  # Returns variation key representing the variation the user will be bucketed in.
  # Returns nil if experiment is not Running, if user is not in experiment, or if datafile is invalid.

  unless @is_valid
    logger = SimpleLogger.new
    logger.log(Logger::ERROR, InvalidDatafileError.new('activate').message)
    return nil
  end

  variation_key = get_variation(experiment_key, user_id, attributes)

  if variation_key.nil?
    @logger.log(Logger::INFO, "Not activating user '#{user_id}'.")
    return nil
  end

  # Create and dispatch impression event
  experiment = @config.get_experiment_from_key(experiment_key)
  send_impression(experiment, variation_key, user_id, attributes)

  variation_key
end

#get_enabled_features(user_id, attributes = nil) ⇒ Object



299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
# File 'lib/optimizely.rb', line 299

def get_enabled_features(user_id, attributes = nil)
  # Gets keys of all feature flags which are enabled for the user.
  # Args:
  #   user_id: ID for user.
  #   attributes: Dict representing user attributes.
  # Returns:
  #   A List of feature flag keys that are enabled for the user.
  #
  enabled_features = []

  unless @is_valid
    logger = SimpleLogger.new
    logger.log(Logger::ERROR, InvalidDatafileError.new('get_enabled_features').message)
    return enabled_features
  end

  @config.feature_flags.each do |feature|
    enabled_features.push(feature['key']) if is_feature_enabled(
      feature['key'],
      user_id,
      attributes
    ) == true
  end
  enabled_features
end

#get_feature_variable_boolean(feature_flag_key, variable_key, user_id, attributes = nil) ⇒ Object



347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
# File 'lib/optimizely.rb', line 347

def get_feature_variable_boolean(feature_flag_key, variable_key, user_id, attributes = nil)
  # Get the Boolean value of the specified variable in the feature flag.
  #
  # feature_flag_key - String key of feature flag the variable belongs to
  # variable_key - String key of variable for which we are getting the string value
  # user_id - String user ID
  # attributes - Hash representing visitor attributes and values which need to be recorded.
  #
  # Returns the boolean variable value.
  # Returns nil if the feature flag or variable are not found.

  variable_value = get_feature_variable_for_type(
    feature_flag_key,
    variable_key,
    Optimizely::Helpers::Constants::VARIABLE_TYPES['BOOLEAN'],
    user_id,
    attributes
  )

  variable_value
end

#get_feature_variable_double(feature_flag_key, variable_key, user_id, attributes = nil) ⇒ Object



369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
# File 'lib/optimizely.rb', line 369

def get_feature_variable_double(feature_flag_key, variable_key, user_id, attributes = nil)
  # Get the Double value of the specified variable in the feature flag.
  #
  # feature_flag_key - String key of feature flag the variable belongs to
  # variable_key - String key of variable for which we are getting the string value
  # user_id - String user ID
  # attributes - Hash representing visitor attributes and values which need to be recorded.
  #
  # Returns the double variable value.
  # Returns nil if the feature flag or variable are not found.

  variable_value = get_feature_variable_for_type(
    feature_flag_key,
    variable_key,
    Optimizely::Helpers::Constants::VARIABLE_TYPES['DOUBLE'],
    user_id,
    attributes
  )

  variable_value
end

#get_feature_variable_integer(feature_flag_key, variable_key, user_id, attributes = nil) ⇒ Object



391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
# File 'lib/optimizely.rb', line 391

def get_feature_variable_integer(feature_flag_key, variable_key, user_id, attributes = nil)
  # Get the Integer value of the specified variable in the feature flag.
  #
  # feature_flag_key - String key of feature flag the variable belongs to
  # variable_key - String key of variable for which we are getting the string value
  # user_id - String user ID
  # attributes - Hash representing visitor attributes and values which need to be recorded.
  #
  # Returns the integer variable value.
  # Returns nil if the feature flag or variable are not found.

  variable_value = get_feature_variable_for_type(
    feature_flag_key,
    variable_key,
    Optimizely::Helpers::Constants::VARIABLE_TYPES['INTEGER'],
    user_id,
    attributes
  )

  variable_value
end

#get_feature_variable_string(feature_flag_key, variable_key, user_id, attributes = nil) ⇒ Object



325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
# File 'lib/optimizely.rb', line 325

def get_feature_variable_string(feature_flag_key, variable_key, user_id, attributes = nil)
  # Get the String value of the specified variable in the feature flag.
  #
  # feature_flag_key - String key of feature flag the variable belongs to
  # variable_key - String key of variable for which we are getting the string value
  # user_id - String user ID
  # attributes - Hash representing visitor attributes and values which need to be recorded.
  #
  # Returns the string variable value.
  # Returns nil if the feature flag or variable are not found.

  variable_value = get_feature_variable_for_type(
    feature_flag_key,
    variable_key,
    Optimizely::Helpers::Constants::VARIABLE_TYPES['STRING'],
    user_id,
    attributes
  )

  variable_value
end

#get_forced_variation(experiment_key, user_id) ⇒ Object



170
171
172
173
174
175
176
177
178
179
180
181
182
183
# File 'lib/optimizely.rb', line 170

def get_forced_variation(experiment_key, user_id)
  # Gets the forced variation for a given user and experiment.
  #
  # experiment_key - String - Key identifying the experiment.
  # user_id - String - The user ID to be used for bucketing.
  #
  # Returns String|nil The forced variation key.

  forced_variation_key = nil
  forced_variation = @config.get_forced_variation(experiment_key, user_id)
  forced_variation_key = forced_variation['key'] if forced_variation

  forced_variation_key
end

#get_variation(experiment_key, user_id, attributes = nil) ⇒ Object



122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
# File 'lib/optimizely.rb', line 122

def get_variation(experiment_key, user_id, attributes = nil)
  # Gets variation where visitor will be bucketed.
  #
  # experiment_key - Experiment for which visitor variation needs to be determined.
  # user_id - String ID for user.
  # attributes - Hash representing user attributes.
  #
  # Returns variation key where visitor will be bucketed.
  # Returns nil if experiment is not Running, if user is not in experiment, or if datafile is invalid.

  unless @is_valid
    logger = SimpleLogger.new
    logger.log(Logger::ERROR, InvalidDatafileError.new('get_variation').message)
    return nil
  end

  if user_id.to_s.empty?
    @logger.log(Logger::ERROR, 'User ID cannot be empty.')
    return nil
  end

  unless user_inputs_valid?(attributes)
    @logger.log(Logger::INFO, "Not activating user '#{user_id}.")
    return nil
  end

  variation_id = @decision_service.get_variation(experiment_key, user_id, attributes)

  unless variation_id.nil?
    variation = @config.get_variation_from_id(experiment_key, variation_id)
    return variation['key'] if variation
  end
  nil
end

#is_feature_enabled(feature_flag_key, user_id, attributes = nil) ⇒ Object



239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
# File 'lib/optimizely.rb', line 239

def is_feature_enabled(feature_flag_key, user_id, attributes = nil)
  # Determine whether a feature is enabled.
  # Sends an impression event if the user is bucketed into an experiment using the feature.
  #
  # feature_flag_key - String unique key of the feature.
  # userId - String ID of the user.
  # attributes - Hash representing visitor attributes and values which need to be recorded.
  #
  # Returns True if the feature is enabled.
  #         False if the feature is disabled.
  #         False if the feature is not found.

  unless @is_valid
    logger = SimpleLogger.new
    logger.log(Logger::ERROR, InvalidDatafileError.new('is_feature_enabled').message)
    return false
  end

  unless feature_flag_key
    @logger.log(Logger::ERROR, 'Feature flag key cannot be empty.')
    return false
  end

  if user_id.to_s.empty?
    @logger.log(Logger::ERROR, 'User ID cannot be empty.')
    return false
  end

  feature_flag = @config.get_feature_flag_from_key(feature_flag_key)
  unless feature_flag
    @logger.log(Logger::ERROR, "No feature flag was found for key '#{feature_flag_key}'.")
    return false
  end

  decision = @decision_service.get_variation_for_feature(feature_flag, user_id, attributes)
  if decision.nil?
    @logger.log(Logger::INFO,
                "Feature '#{feature_flag_key}' is not enabled for user '#{user_id}'.")
    return false
  end

  variation = decision['variation']
  unless variation['featureEnabled']
    @logger.log(Logger::INFO,
                "Feature '#{feature_flag_key}' is not enabled for user '#{user_id}'.")
    return false
  end

  if decision.source == Optimizely::DecisionService::DECISION_SOURCE_EXPERIMENT
    # Send event if Decision came from an experiment.
    send_impression(decision.experiment, variation['key'], user_id, attributes)
  else
    @logger.log(Logger::DEBUG,
                "The user '#{user_id}' is not being experimented on in feature '#{feature_flag_key}'.")
  end
  @logger.log(Logger::INFO, "Feature '#{feature_flag_key}' is enabled for user '#{user_id}'.")

  true
end

#set_forced_variation(experiment_key, user_id, variation_key) ⇒ Object



157
158
159
160
161
162
163
164
165
166
167
168
# File 'lib/optimizely.rb', line 157

def set_forced_variation(experiment_key, user_id, variation_key)
  # Force a user into a variation for a given experiment.
  #
  # experiment_key - String - key identifying the experiment.
  # user_id - String - The user ID to be used for bucketing.
  # variation_key - The variation key specifies the variation which the user will
  #   be forced into. If nil, then clear the existing experiment-to-variation mapping.
  #
  # Returns - Boolean - indicates if the set completed successfully.

  @config.set_forced_variation(experiment_key, user_id, variation_key)
end

#track(event_key, user_id, attributes = nil, event_tags = nil) ⇒ Object



185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
# File 'lib/optimizely.rb', line 185

def track(event_key, user_id, attributes = nil, event_tags = nil)
  # Send conversion event to Optimizely.
  #
  # event_key - Event key representing the event which needs to be recorded.
  # user_id - String ID for user.
  # attributes - Hash representing visitor attributes and values which need to be recorded.
  # event_tags - Hash representing metadata associated with the event.

  unless @is_valid
    logger = SimpleLogger.new
    logger.log(Logger::ERROR, InvalidDatafileError.new('track').message)
    return nil
  end

  if user_id.to_s.empty?
    @logger.log(Logger::ERROR, 'User ID cannot be empty.')
    return nil
  end

  return nil unless user_inputs_valid?(attributes, event_tags)

  experiment_ids = @config.get_experiment_ids_for_event(event_key)
  if experiment_ids.empty?
    @config.logger.log(Logger::INFO, "Not tracking user '#{user_id}'.")
    return nil
  end

  # Filter out experiments that are not running or that do not include the user in audience conditions

  experiment_variation_map = get_valid_experiments_for_event(event_key, user_id, attributes)

  # Don't track events without valid experiments attached
  if experiment_variation_map.empty?
    @logger.log(Logger::INFO, "There are no valid experiments for event '#{event_key}' to track.")
    return nil
  end

  conversion_event = @event_builder.create_conversion_event(event_key, user_id, attributes,
                                                            event_tags, experiment_variation_map)
  @logger.log(Logger::INFO,
              "Dispatching conversion event to URL #{conversion_event.url} with params #{conversion_event.params}.")
  begin
    @event_dispatcher.dispatch_event(conversion_event)
  rescue => e
    @logger.log(Logger::ERROR, "Unable to dispatch conversion event. Error: #{e}")
  end

  @notification_center.send_notifications(
    NotificationCenter::NOTIFICATION_TYPES[:TRACK],
    event_key, user_id, attributes, event_tags, conversion_event
  )
  nil
end