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

Constructor for Projects.

Parameters:

  • datafile
    • JSON string representing the project.

  • event_dispatcher (defaults to: nil)
    • Provides a dispatch_event method which if given a URL and params sends a request to it.

  • logger (defaults to: nil)
    • Optional component which provides a log method to log messages. By default nothing would be logged.

  • error_handler (defaults to: nil)
    • Optional component which provides a handle_error method to handle exceptions.

    By default all exceptions will be suppressed.

  • user_profile_service (defaults to: nil)
    • Optional component which provides methods to store and retreive user profiles.

  • skip_json_validation (defaults to: false)
    • Optional boolean param to skip JSON schema validation of the provided datafile.



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
# File 'lib/optimizely.rb', line 49

def initialize(datafile, event_dispatcher = nil, logger = nil, error_handler = nil, skip_json_validation = false,  = nil)
  @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 StandardError => e
    @is_valid = false
    @logger = SimpleLogger.new
    error_msg = e.class == InvalidDatafileVersionError ? e.message : InvalidInputError.new('datafile').message
    error_to_handle = e.class == InvalidDatafileVersionError ? InvalidDatafileVersionError : InvalidInputError
    @logger.log(Logger::ERROR, error_msg)
    @error_handler.handle_error error_to_handle
    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)



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

def config
  @config
end

#decision_serviceObject (readonly)



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

def decision_service
  @decision_service
end

#error_handlerObject (readonly)



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

def error_handler
  @error_handler
end

#event_builderObject (readonly)



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

def event_builder
  @event_builder
end

#event_dispatcherObject (readonly)



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

def event_dispatcher
  @event_dispatcher
end

#is_validObject (readonly)



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

def is_valid
  @is_valid
end

#loggerObject (readonly)



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

def logger
  @logger
end

#notification_centerObject (readonly)

Returns the value of attribute notification_center.



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

def notification_center
  @notification_center
end

Instance Method Details

#activate(experiment_key, user_id, attributes = nil) ⇒ Variation Key?

Buckets visitor and sends impression event to Optimizely.

Parameters:

  • experiment_key
    • Experiment which needs to be activated.

  • user_id
    • String ID for user.

  • attributes (defaults to: nil)
    • Hash representing user attributes and values to be recorded.

Returns:

  • (Variation Key)

    representing the variation the user will be bucketed in.

  • (nil)

    if experiment is not Running, if user is not in experiment, or if datafile is invalid.



91
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
# File 'lib/optimizely.rb', line 91

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

  return nil unless Optimizely::Helpers::Validator.inputs_valid?(
    {
      experiment_key: experiment_key,
      user_id: user_id
    }, @logger, Logger::ERROR
  )

  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) ⇒ feature flag keys

Gets keys of all feature flags which are enabled for the user.

Parameters:

  • user_id
    • ID for user.

  • attributes (defaults to: nil)
    • Dict representing user attributes.

Returns:

  • (feature flag keys)

    A List of feature flag keys that are enabled for the user.



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

def get_enabled_features(user_id, attributes = nil)
  enabled_features = []

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

  return enabled_features unless Optimizely::Helpers::Validator.inputs_valid?({user_id: user_id}, @logger, Logger::ERROR)

  @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) ⇒ Boolean?

Get the Boolean value of the specified variable in the feature flag.

Parameters:

  • 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 (defaults to: nil)
    • Hash representing visitor attributes and values which need to be recorded.

Returns:

  • (Boolean)

    the boolean variable value.

  • (nil)

    if the feature flag or variable are not found.



357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
# File 'lib/optimizely.rb', line 357

def get_feature_variable_boolean(feature_flag_key, variable_key, user_id, attributes = nil)
  unless @is_valid
    @logger.log(Logger::ERROR, InvalidDatafileError.new('get_feature_variable_boolean').message)
    return nil
  end

  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) ⇒ Boolean?

Get the Double value of the specified variable in the feature flag.

Parameters:

  • 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 (defaults to: nil)
    • Hash representing visitor attributes and values which need to be recorded.

Returns:

  • (Boolean)

    the double variable value.

  • (nil)

    if the feature flag or variable are not found.



384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
# File 'lib/optimizely.rb', line 384

def get_feature_variable_double(feature_flag_key, variable_key, user_id, attributes = nil)
  unless @is_valid
    @logger.log(Logger::ERROR, InvalidDatafileError.new('get_feature_variable_double').message)
    return nil
  end

  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) ⇒ Integer?

Get the Integer value of the specified variable in the feature flag.

Parameters:

  • 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 (defaults to: nil)
    • Hash representing visitor attributes and values which need to be recorded.

Returns:

  • (Integer)

    variable value.

  • (nil)

    if the feature flag or variable are not found.



411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
# File 'lib/optimizely.rb', line 411

def get_feature_variable_integer(feature_flag_key, variable_key, user_id, attributes = nil)
  unless @is_valid
    @logger.log(Logger::ERROR, InvalidDatafileError.new('get_feature_variable_integer').message)
    return nil
  end
  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) ⇒ String?

Get the String value of the specified variable in the feature flag.

Parameters:

  • 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 (defaults to: nil)
    • Hash representing visitor attributes and values which need to be recorded.

Returns:

  • (String)

    the string variable value.

  • (nil)

    if the feature flag or variable are not found.



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

def get_feature_variable_string(feature_flag_key, variable_key, user_id, attributes = nil)
  unless @is_valid
    @logger.log(Logger::ERROR, InvalidDatafileError.new('get_feature_variable_string').message)
    return nil
  end

  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) ⇒ String

Gets the forced variation for a given user and experiment.

Parameters:

  • experiment_key
    • String - Key identifying the experiment.

  • user_id
    • String - The user ID to be used for bucketing.

Returns:

  • (String)

    The forced variation key.



174
175
176
177
178
179
180
# File 'lib/optimizely.rb', line 174

def get_forced_variation(experiment_key, user_id)
  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) ⇒ variation key?

Gets variation where visitor will be bucketed.

Parameters:

  • experiment_key
    • Experiment for which visitor variation needs to be determined.

  • user_id
    • String ID for user.

  • attributes (defaults to: nil)
    • Hash representing user attributes.

Returns:

  • (variation key)

    where visitor will be bucketed.

  • (nil)

    if experiment is not Running, if user is not in experiment, or if datafile is invalid.



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
# File 'lib/optimizely.rb', line 127

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

  return nil unless Optimizely::Helpers::Validator.inputs_valid?(
    {
      experiment_key: experiment_key,
      user_id: user_id
    }, @logger, Logger::ERROR
  )

  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) ⇒ True, False

Determine whether a feature is enabled. Sends an impression event if the user is bucketed into an experiment using the feature.

Parameters:

  • feature_flag_key
    • String unique key of the feature.

  • user_id
    • String ID of the user.

  • attributes (defaults to: nil)
    • 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.



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
# File 'lib/optimizely.rb', line 248

def is_feature_enabled(feature_flag_key, user_id, attributes = nil)
  unless @is_valid
    @logger.log(Logger::ERROR, InvalidDatafileError.new('is_feature_enabled').message)
    return false
  end

  return false unless Optimizely::Helpers::Validator.inputs_valid?(
    {
      feature_flag_key: feature_flag_key,
      user_id: user_id
    }, @logger, Logger::ERROR
  )

  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']
  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

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

#set_forced_variation(experiment_key, user_id, variation_key) ⇒ Boolean

Force a user into a variation for a given experiment.

Parameters:

  • 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.



163
164
165
# File 'lib/optimizely.rb', line 163

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

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

Send conversion event to Optimizely.

Parameters:

  • event_key
    • Event key representing the event which needs to be recorded.

  • user_id
    • String ID for user.

  • attributes (defaults to: nil)
    • Hash representing visitor attributes and values which need to be recorded.

  • event_tags (defaults to: nil)
    • Hash representing metadata associated with the event.



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
# File 'lib/optimizely.rb', line 189

def track(event_key, user_id, attributes = nil, event_tags = nil)
  unless @is_valid
    @logger.log(Logger::ERROR, InvalidDatafileError.new('track').message)
    return nil
  end

  return nil unless Optimizely::Helpers::Validator.inputs_valid?(
    {
      event_key: event_key,
      user_id: user_id
    }, @logger, Logger::ERROR
  )

  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