class Optimizely::Project

Attributes

config_manager[R]

@api no-doc

decision_service[R]

@api no-doc

error_handler[R]

@api no-doc

event_dispatcher[R]

@api no-doc

event_processor[R]

@api no-doc

logger[R]

@api no-doc

notification_center[R]
stopped[R]

@api no-doc

Public Class Methods

new( datafile = nil, event_dispatcher = nil, logger = nil, error_handler = nil, skip_json_validation = false, user_profile_service = nil, sdk_key = nil, config_manager = nil, notification_center = nil, event_processor = nil, default_decide_options = [] ) click to toggle source

Constructor for Projects.

@param datafile - JSON string representing the project. @param event_dispatcher - Provides a dispatch_event method which if given a URL and params sends a request to it. @param logger - Optional component which provides a log method to log messages. By default nothing would be logged. @param error_handler - Optional component which provides a handle_error method to handle exceptions.

By default all exceptions will be suppressed.

@param user_profile_service - Optional component which provides methods to store and retreive user profiles. @param skip_json_validation - Optional boolean param to skip JSON schema validation of the provided datafile. @params sdk_key - Optional string uniquely identifying the datafile corresponding to project and environment combination.

Must provide at least one of datafile or sdk_key.

@param config_manager - Optional Responds to 'config' method. @param notification_center - Optional Instance of NotificationCenter. @param event_processor - Optional Responds to process.

# File lib/optimizely.rb, line 66
def initialize(
  datafile = nil,
  event_dispatcher = nil,
  logger = nil,
  error_handler = nil,
  skip_json_validation = false,
  user_profile_service = nil,
  sdk_key = nil,
  config_manager = nil,
  notification_center = nil,
  event_processor = nil,
  default_decide_options = []
)
  @logger = logger || NoOpLogger.new
  @error_handler = error_handler || NoOpErrorHandler.new
  @event_dispatcher = event_dispatcher || EventDispatcher.new(logger: @logger, error_handler: @error_handler)
  @user_profile_service = user_profile_service
  @default_decide_options = []

  if default_decide_options.is_a? Array
    @default_decide_options = default_decide_options.clone
  else
    @logger.log(Logger::DEBUG, 'Provided default decide options is not an array.')
    @default_decide_options = []
  end

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

  @notification_center = notification_center.is_a?(Optimizely::NotificationCenter) ? notification_center : NotificationCenter.new(@logger, @error_handler)

  @config_manager = if config_manager.respond_to?(:config)
                      config_manager
                    elsif sdk_key
                      HTTPProjectConfigManager.new(
                        sdk_key: sdk_key,
                        datafile: datafile,
                        logger: @logger,
                        error_handler: @error_handler,
                        skip_json_validation: skip_json_validation,
                        notification_center: @notification_center
                      )
                    else
                      StaticProjectConfigManager.new(datafile, @logger, @error_handler, skip_json_validation)
                    end

  @decision_service = DecisionService.new(@logger, @user_profile_service)

  @event_processor = if event_processor.respond_to?(:process)
                       event_processor
                     else
                       ForwardingEventProcessor.new(@event_dispatcher, @logger, @notification_center)
                     end
end

Public Instance Methods

activate(experiment_key, user_id, attributes = nil) click to toggle source

Buckets visitor and sends impression event to Optimizely.

@param experiment_key - Experiment which needs to be activated. @param user_id - String ID for user. @param attributes - Hash representing user attributes and values to be recorded.

@return [Variation Key] representing the variation the user will be bucketed in. @return [nil] if experiment is not Running, if user is not in experiment, or if datafile is invalid.

# File lib/optimizely.rb, line 303
def activate(experiment_key, user_id, attributes = nil)
  unless is_valid
    @logger.log(Logger::ERROR, InvalidProjectConfigError.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
  )

  config = project_config

  variation_key = get_variation_with_config(experiment_key, user_id, attributes, config)

  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(
    config, experiment, variation_key, '', experiment_key, true,
    Optimizely::DecisionService::DECISION_SOURCES['EXPERIMENT'], user_id, attributes
  )

  variation_key
end
close() click to toggle source
# File lib/optimizely.rb, line 806
def close
  return if @stopped

  @stopped = true
  @config_manager.stop! if @config_manager.respond_to?(:stop!)
  @event_processor.stop! if @event_processor.respond_to?(:stop!)
end
create_user_context(user_id, attributes = nil) click to toggle source

Create a context of the user for which decision APIs will be called.

A user context will be created successfully even when the SDK is not fully configured yet.

@param user_id - The user ID to be used for bucketing. @param attributes - A Hash representing user attribute names and values.

@return [OptimizelyUserContext] An OptimizelyUserContext associated with this OptimizelyClient. @return [nil] If user attributes are not in valid format.

# File lib/optimizely.rb, line 135
def create_user_context(user_id, attributes = nil)
  # We do not check for is_valid here as a user context can be created successfully
  # even when the SDK is not fully configured.

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

  # validate attributes
  return nil unless user_inputs_valid?(attributes)

  user_context = OptimizelyUserContext.new(self, user_id, attributes)
  user_context
end
decide(user_context, key, decide_options = []) click to toggle source
# File lib/optimizely.rb, line 153
def decide(user_context, key, decide_options = [])
  # raising on user context as it is internal and not provided directly by the user.
  raise if user_context.class != OptimizelyUserContext

  reasons = []

  # check if SDK is ready
  unless is_valid
    @logger.log(Logger::ERROR, InvalidProjectConfigError.new('decide').message)
    reasons.push(OptimizelyDecisionMessage::SDK_NOT_READY)
    return OptimizelyDecision.new(flag_key: key, user_context: user_context, reasons: reasons)
  end

  # validate that key is a string
  unless key.is_a?(String)
    @logger.log(Logger::ERROR, 'Provided key is invalid')
    reasons.push(format(OptimizelyDecisionMessage::FLAG_KEY_INVALID, key))
    return OptimizelyDecision.new(flag_key: key, user_context: user_context, reasons: reasons)
  end

  # validate that key maps to a feature flag
  config = project_config
  feature_flag = config.get_feature_flag_from_key(key)
  unless feature_flag
    @logger.log(Logger::ERROR, "No feature flag was found for key '#{key}'.")
    reasons.push(format(OptimizelyDecisionMessage::FLAG_KEY_INVALID, key))
    return OptimizelyDecision.new(flag_key: key, user_context: user_context, reasons: reasons)
  end

  # merge decide_options and default_decide_options
  if decide_options.is_a? Array
    decide_options += @default_decide_options
  else
    @logger.log(Logger::DEBUG, 'Provided decide options is not an array. Using default decide options.')
    decide_options = @default_decide_options
  end

  # Create Optimizely Decision Result.
  user_id = user_context.user_id
  attributes = user_context.user_attributes
  variation_key = nil
  feature_enabled = false
  rule_key = nil
  flag_key = key
  all_variables = {}
  decision_event_dispatched = false
  experiment = nil
  decision_source = Optimizely::DecisionService::DECISION_SOURCES['ROLLOUT']

  decision, reasons_received = @decision_service.get_variation_for_feature(config, feature_flag, user_id, attributes, decide_options)
  reasons.push(*reasons_received)

  # Send impression event if Decision came from a feature test and decide options doesn't include disableDecisionEvent
  if decision.is_a?(Optimizely::DecisionService::Decision)
    experiment = decision.experiment
    rule_key = experiment['key']
    variation = decision['variation']
    variation_key = variation['key']
    feature_enabled = variation['featureEnabled']
    decision_source = decision.source
  end

  unless decide_options.include? OptimizelyDecideOption::DISABLE_DECISION_EVENT
    if decision_source == Optimizely::DecisionService::DECISION_SOURCES['FEATURE_TEST'] || config.send_flag_decisions
      send_impression(config, experiment, variation_key || '', flag_key, rule_key || '', feature_enabled, decision_source, user_id, attributes)
      decision_event_dispatched = true
    end
  end

  # Generate all variables map if decide options doesn't include excludeVariables
  unless decide_options.include? OptimizelyDecideOption::EXCLUDE_VARIABLES
    feature_flag['variables'].each do |variable|
      variable_value = get_feature_variable_for_variation(key, feature_enabled, variation, variable, user_id)
      all_variables[variable['key']] = Helpers::VariableType.cast_value_to_type(variable_value, variable['type'], @logger)
    end
  end

  should_include_reasons = decide_options.include? OptimizelyDecideOption::INCLUDE_REASONS

  # Send notification
  @notification_center.send_notifications(
    NotificationCenter::NOTIFICATION_TYPES[:DECISION],
    Helpers::Constants::DECISION_NOTIFICATION_TYPES['FLAG'],
    user_id, (attributes || {}),
    flag_key: flag_key,
    enabled: feature_enabled,
    variables: all_variables,
    variation_key: variation_key,
    rule_key: rule_key,
    reasons: should_include_reasons ? reasons : [],
    decision_event_dispatched: decision_event_dispatched
  )

  OptimizelyDecision.new(
    variation_key: variation_key,
    enabled: feature_enabled,
    variables: all_variables,
    rule_key: rule_key,
    flag_key: flag_key,
    user_context: user_context,
    reasons: should_include_reasons ? reasons : []
  )
end
decide_all(user_context, decide_options = []) click to toggle source
# File lib/optimizely.rb, line 257
def decide_all(user_context, decide_options = [])
  # raising on user context as it is internal and not provided directly by the user.
  raise if user_context.class != OptimizelyUserContext

  # check if SDK is ready
  unless is_valid
    @logger.log(Logger::ERROR, InvalidProjectConfigError.new('decide_all').message)
    return {}
  end

  keys = []
  project_config.feature_flags.each do |feature_flag|
    keys.push(feature_flag['key'])
  end
  decide_for_keys(user_context, keys, decide_options)
end
decide_for_keys(user_context, keys, decide_options = []) click to toggle source
# File lib/optimizely.rb, line 274
def decide_for_keys(user_context, keys, decide_options = [])
  # raising on user context as it is internal and not provided directly by the user.
  raise if user_context.class != OptimizelyUserContext

  # check if SDK is ready
  unless is_valid
    @logger.log(Logger::ERROR, InvalidProjectConfigError.new('decide_for_keys').message)
    return {}
  end

  enabled_flags_only = (!decide_options.nil? && (decide_options.include? OptimizelyDecideOption::ENABLED_FLAGS_ONLY)) || (@default_decide_options.include? OptimizelyDecideOption::ENABLED_FLAGS_ONLY)

  decisions = {}
  keys.each do |key|
    decision = decide(user_context, key, decide_options)
    decisions[key] = decision unless enabled_flags_only && !decision.enabled
  end
  decisions
end
get_all_feature_variables(feature_flag_key, user_id, attributes = nil) click to toggle source

Get values of all the variables in the feature flag and returns them in a Dict

@param feature_flag_key - String key of feature flag @param user_id - String user ID @param attributes - Hash representing visitor attributes and values which need to be recorded.

@return [Dict] the Dict containing all the varible values @return [nil] if the feature flag is not found.

# File lib/optimizely.rb, line 718
def get_all_feature_variables(feature_flag_key, user_id, attributes = nil)
  unless is_valid
    @logger.log(Logger::ERROR, InvalidProjectConfigError.new('get_all_feature_variables').message)
    return nil
  end

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

  return nil unless user_inputs_valid?(attributes)

  config = project_config

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

  decision, = @decision_service.get_variation_for_feature(config, feature_flag, user_id, attributes)
  variation = decision ? decision['variation'] : nil
  feature_enabled = variation ? variation['featureEnabled'] : false
  all_variables = {}

  feature_flag['variables'].each do |variable|
    variable_value = get_feature_variable_for_variation(feature_flag_key, feature_enabled, variation, variable, user_id)
    all_variables[variable['key']] = Helpers::VariableType.cast_value_to_type(variable_value, variable['type'], @logger)
  end

  source_string = Optimizely::DecisionService::DECISION_SOURCES['ROLLOUT']
  if decision && decision['source'] == Optimizely::DecisionService::DECISION_SOURCES['FEATURE_TEST']
    source_info = {
      experiment_key: decision.experiment['key'],
      variation_key: variation['key']
    }
    source_string = Optimizely::DecisionService::DECISION_SOURCES['FEATURE_TEST']
  end

  @notification_center.send_notifications(
    NotificationCenter::NOTIFICATION_TYPES[:DECISION],
    Helpers::Constants::DECISION_NOTIFICATION_TYPES['ALL_FEATURE_VARIABLES'], user_id, (attributes || {}),
    feature_key: feature_flag_key,
    feature_enabled: feature_enabled,
    source: source_string,
    variable_values: all_variables,
    source_info: source_info || {}
  )

  all_variables
end
get_enabled_features(user_id, attributes = nil) click to toggle source

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

@param user_id - ID for user. @param attributes - Dict representing user attributes. @return [feature flag keys] A List of feature flag keys that are enabled for the user.

# File lib/optimizely.rb, line 550
def get_enabled_features(user_id, attributes = nil)
  enabled_features = []
  unless is_valid
    @logger.log(Logger::ERROR, InvalidProjectConfigError.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
  )

  return enabled_features unless user_inputs_valid?(attributes)

  config = project_config

  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(feature_flag_key, variable_key, user_id, attributes = nil) click to toggle source

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

@param feature_flag_key - String key of feature flag the variable belongs to @param variable_key - String key of variable for which we are getting the value @param user_id - String user ID @param attributes - Hash representing visitor attributes and values which need to be recorded.

@return [*] the type-casted variable value. @return [nil] if the feature flag or variable are not found.

# File lib/optimizely.rb, line 587
def get_feature_variable(feature_flag_key, variable_key, user_id, attributes = nil)
  unless is_valid
    @logger.log(Logger::ERROR, InvalidProjectConfigError.new('get_feature_variable').message)
    return nil
  end
  variable_value = get_feature_variable_for_type(
    feature_flag_key,
    variable_key,
    nil,
    user_id,
    attributes
  )

  variable_value
end
get_feature_variable_boolean(feature_flag_key, variable_key, user_id, attributes = nil) click to toggle source

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

@param feature_flag_key - String key of feature flag the variable belongs to @param variable_key - String key of variable for which we are getting the string value @param user_id - String user ID @param attributes - Hash representing visitor attributes and values which need to be recorded.

@return [Boolean] the boolean variable value. @return [nil] if the feature flag or variable are not found.

# File lib/optimizely.rb, line 665
def get_feature_variable_boolean(feature_flag_key, variable_key, user_id, attributes = nil)
  unless is_valid
    @logger.log(Logger::ERROR, InvalidProjectConfigError.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) click to toggle source

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

@param feature_flag_key - String key of feature flag the variable belongs to @param variable_key - String key of variable for which we are getting the string value @param user_id - String user ID @param attributes - Hash representing visitor attributes and values which need to be recorded.

@return [Boolean] the double variable value. @return [nil] if the feature flag or variable are not found.

# File lib/optimizely.rb, line 692
def get_feature_variable_double(feature_flag_key, variable_key, user_id, attributes = nil)
  unless is_valid
    @logger.log(Logger::ERROR, InvalidProjectConfigError.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) click to toggle source

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

@param feature_flag_key - String key of feature flag the variable belongs to @param variable_key - String key of variable for which we are getting the string value @param user_id - String user ID @param attributes - Hash representing visitor attributes and values which need to be recorded.

@return [Integer] variable value. @return [nil] if the feature flag or variable are not found.

# File lib/optimizely.rb, line 784
def get_feature_variable_integer(feature_flag_key, variable_key, user_id, attributes = nil)
  unless is_valid
    @logger.log(Logger::ERROR, InvalidProjectConfigError.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_json(feature_flag_key, variable_key, user_id, attributes = nil) click to toggle source

Get the Json value of the specified variable in the feature flag in a Dict.

@param feature_flag_key - String key of feature flag the variable belongs to @param variable_key - String key of variable for which we are getting the string value @param user_id - String user ID @param attributes - Hash representing visitor attributes and values which need to be recorded.

@return [Dict] the Dict containing variable value. @return [nil] if the feature flag or variable are not found.

# File lib/optimizely.rb, line 639
def get_feature_variable_json(feature_flag_key, variable_key, user_id, attributes = nil)
  unless is_valid
    @logger.log(Logger::ERROR, InvalidProjectConfigError.new('get_feature_variable_json').message)
    return nil
  end
  variable_value = get_feature_variable_for_type(
    feature_flag_key,
    variable_key,
    Optimizely::Helpers::Constants::VARIABLE_TYPES['JSON'],
    user_id,
    attributes
  )

  variable_value
end
get_feature_variable_string(feature_flag_key, variable_key, user_id, attributes = nil) click to toggle source

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

@param feature_flag_key - String key of feature flag the variable belongs to @param variable_key - String key of variable for which we are getting the string value @param user_id - String user ID @param attributes - Hash representing visitor attributes and values which need to be recorded.

@return [String] the string variable value. @return [nil] if the feature flag or variable are not found.

# File lib/optimizely.rb, line 613
def get_feature_variable_string(feature_flag_key, variable_key, user_id, attributes = nil)
  unless is_valid
    @logger.log(Logger::ERROR, InvalidProjectConfigError.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) click to toggle source

Gets the forced variation for a given user and experiment.

@param experiment_key - String - Key identifying the experiment. @param user_id - String - The user ID to be used for bucketing.

@return [String] The forced variation key.

# File lib/optimizely.rb, line 393
def get_forced_variation(experiment_key, user_id)
  unless is_valid
    @logger.log(Logger::ERROR, InvalidProjectConfigError.new('get_forced_variation').message)
    return nil
  end

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

  config = project_config

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

  forced_variation_key
end
get_optimizely_config() click to toggle source
# File lib/optimizely.rb, line 814
def get_optimizely_config
  # Get OptimizelyConfig object containing experiments and features data
  # Returns Object
  #
  # OptimizelyConfig Object Schema
  # {
  #   'experimentsMap' => {
  #     'my-fist-experiment' => {
  #       'id' => '111111',
  #       'key' => 'my-fist-experiment'
  #       'variationsMap' => {
  #         'variation_1' => {
  #           'id' => '121212',
  #           'key' => 'variation_1',
  #           'variablesMap' => {
  #             'age' => {
  #               'id' => '222222',
  #               'key' => 'age',
  #               'type' => 'integer',
  #               'value' => '0',
  #             }
  #           }
  #         }
  #       }
  #     }
  #   },
  #   'featuresMap' => {
  #     'awesome-feature' => {
  #       'id' => '333333',
  #       'key' => 'awesome-feature',
  #       'experimentsMap' => Object,
  #       'variablesMap' => Object,
  #     }
  #   },
  #   'revision' => '13',
  # }
  #
  unless is_valid
    @logger.log(Logger::ERROR, InvalidProjectConfigError.new('get_optimizely_config').message)
    return nil
  end

  # config_manager might not contain optimizely_config if its supplied by the consumer
  # Generating a new OptimizelyConfig object in this case as a fallback
  if @config_manager.respond_to?(:optimizely_config)
    @config_manager.optimizely_config
  else
    OptimizelyConfig.new(project_config).config
  end
end
get_variation(experiment_key, user_id, attributes = nil) click to toggle source

Gets variation where visitor will be bucketed.

@param experiment_key - Experiment for which visitor variation needs to be determined. @param user_id - String ID for user. @param attributes - Hash representing user attributes.

@return [variation key] where visitor will be bucketed. @return [nil] if experiment is not Running, if user is not in experiment, or if datafile is invalid.

# File lib/optimizely.rb, line 344
def get_variation(experiment_key, user_id, attributes = nil)
  unless is_valid
    @logger.log(Logger::ERROR, InvalidProjectConfigError.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
  )

  config = project_config

  get_variation_with_config(experiment_key, user_id, attributes, config)
end
is_feature_enabled(feature_flag_key, user_id, attributes = nil) click to toggle source

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

@param feature_flag_key - String unique key of the feature. @param user_id - String ID of the user. @param attributes - Hash representing visitor attributes and values which need to be recorded.

@return [True] if the feature is enabled. @return [False] if the feature is disabled. @return [False] if the feature is not found.

# File lib/optimizely.rb, line 470
def is_feature_enabled(feature_flag_key, user_id, attributes = nil)
  unless is_valid
    @logger.log(Logger::ERROR, InvalidProjectConfigError.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
  )

  return false unless user_inputs_valid?(attributes)

  config = project_config

  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(config, feature_flag, user_id, attributes)

  feature_enabled = false
  source_string = Optimizely::DecisionService::DECISION_SOURCES['ROLLOUT']
  if decision.is_a?(Optimizely::DecisionService::Decision)
    variation = decision['variation']
    feature_enabled = variation['featureEnabled']
    if decision.source == Optimizely::DecisionService::DECISION_SOURCES['FEATURE_TEST']
      source_string = Optimizely::DecisionService::DECISION_SOURCES['FEATURE_TEST']
      source_info = {
        experiment_key: decision.experiment['key'],
        variation_key: variation['key']
      }
      # Send event if Decision came from a feature test.
      send_impression(
        config, decision.experiment, variation['key'], feature_flag_key, decision.experiment['key'], feature_enabled, source_string, user_id, attributes
      )
    elsif decision.source == Optimizely::DecisionService::DECISION_SOURCES['ROLLOUT'] && config.send_flag_decisions
      send_impression(
        config, decision.experiment, variation['key'], feature_flag_key, decision.experiment['key'], feature_enabled, source_string, user_id, attributes
      )
    end
  end

  if decision.nil? && config.send_flag_decisions
    send_impression(
      config, nil, '', feature_flag_key, '', feature_enabled, source_string, user_id, attributes
    )
  end

  @notification_center.send_notifications(
    NotificationCenter::NOTIFICATION_TYPES[:DECISION],
    Helpers::Constants::DECISION_NOTIFICATION_TYPES['FEATURE'],
    user_id, (attributes || {}),
    feature_key: feature_flag_key,
    feature_enabled: feature_enabled,
    source: source_string,
    source_info: source_info || {}
  )

  if feature_enabled == true
    @logger.log(Logger::INFO,
                "Feature '#{feature_flag_key}' is enabled for user '#{user_id}'.")
    return true
  end

  @logger.log(Logger::INFO,
              "Feature '#{feature_flag_key}' is not enabled for user '#{user_id}'.")
  false
end
is_valid() click to toggle source
# File lib/optimizely.rb, line 801
def is_valid
  config = project_config
  config.is_a?(Optimizely::ProjectConfig)
end
set_forced_variation(experiment_key, user_id, variation_key) click to toggle source

Force a user into a variation for a given experiment.

@param experiment_key - String - key identifying the experiment. @param user_id - String - The user ID to be used for bucketing. @param 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.

@return [Boolean] indicates if the set completed successfully.

# File lib/optimizely.rb, line 371
def set_forced_variation(experiment_key, user_id, variation_key)
  unless is_valid
    @logger.log(Logger::ERROR, InvalidProjectConfigError.new('set_forced_variation').message)
    return nil
  end

  input_values = {experiment_key: experiment_key, user_id: user_id}
  input_values[:variation_key] = variation_key unless variation_key.nil?
  return false unless Optimizely::Helpers::Validator.inputs_valid?(input_values, @logger, Logger::ERROR)

  config = project_config

  @decision_service.set_forced_variation(config, experiment_key, user_id, variation_key)
end
track(event_key, user_id, attributes = nil, event_tags = nil) click to toggle source

Send conversion event to Optimizely.

@param event_key - Event key representing the event which needs to be recorded. @param user_id - String ID for user. @param attributes - Hash representing visitor attributes and values which need to be recorded. @param event_tags - Hash representing metadata associated with the event.

# File lib/optimizely.rb, line 422
def track(event_key, user_id, attributes = nil, event_tags = nil)
  unless is_valid
    @logger.log(Logger::ERROR, InvalidProjectConfigError.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)

  config = project_config

  event = config.get_event_from_key(event_key)
  unless event
    @logger.log(Logger::INFO, "Not tracking user '#{user_id}' for event '#{event_key}'.")
    return nil
  end

  user_event = UserEventFactory.create_conversion_event(config, event, user_id, attributes, event_tags)
  @event_processor.process(user_event)
  @logger.log(Logger::INFO, "Tracking event '#{event_key}' for user '#{user_id}'.")

  if @notification_center.notification_count(NotificationCenter::NOTIFICATION_TYPES[:TRACK]).positive?
    log_event = EventFactory.create_log_event(user_event, @logger)
    @notification_center.send_notifications(
      NotificationCenter::NOTIFICATION_TYPES[:TRACK],
      event_key, user_id, attributes, event_tags, log_event
    )
  end
  nil
end

Private Instance Methods

attributes_valid?(attributes) click to toggle source
# File lib/optimizely.rb, line 1036
def attributes_valid?(attributes)
  unless Helpers::Validator.attributes_valid?(attributes)
    @logger.log(Logger::ERROR, 'Provided attributes are in an invalid format.')
    @error_handler.handle_error(InvalidAttributeFormatError)
    return false
  end
  true
end
event_tags_valid?(event_tags) click to toggle source
# File lib/optimizely.rb, line 1045
def event_tags_valid?(event_tags)
  unless Helpers::Validator.event_tags_valid?(event_tags)
    @logger.log(Logger::ERROR, 'Provided event tags are in an invalid format.')
    @error_handler.handle_error(InvalidEventTagFormatError)
    return false
  end
  true
end
get_feature_variable_for_type(feature_flag_key, variable_key, variable_type, user_id, attributes = nil) click to toggle source
# File lib/optimizely.rb, line 902
def get_feature_variable_for_type(feature_flag_key, variable_key, variable_type, user_id, attributes = nil)
  # Get the variable value for the given feature variable and cast it to the specified type
  # The default value is returned if the feature flag is not enabled for the user.
  #
  # 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
  # variable_type - String requested type for feature variable
  # user_id - String user ID
  # attributes - Hash representing visitor attributes and values which need to be recorded.
  #
  # Returns the type-casted variable value.
  # Returns nil if the feature flag or variable or user ID is empty
  #             in case of variable type mismatch

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

  return nil unless user_inputs_valid?(attributes)

  config = project_config

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

  variable = config.get_feature_variable(feature_flag, variable_key)

  # Error message logged in DatafileProjectConfig- get_feature_flag_from_key
  return nil if variable.nil?

  # If variable_type is nil, set it equal to variable['type']
  variable_type ||= variable['type']
  # Returns nil if type differs
  if variable['type'] != variable_type
    @logger.log(Logger::WARN,
                "Requested variable as type '#{variable_type}' but variable '#{variable_key}' is of type '#{variable['type']}'.")
    return nil
  end

  decision, = @decision_service.get_variation_for_feature(config, feature_flag, user_id, attributes)
  variation = decision ? decision['variation'] : nil
  feature_enabled = variation ? variation['featureEnabled'] : false

  variable_value = get_feature_variable_for_variation(feature_flag_key, feature_enabled, variation, variable, user_id)
  variable_value = Helpers::VariableType.cast_value_to_type(variable_value, variable_type, @logger)

  source_string = Optimizely::DecisionService::DECISION_SOURCES['ROLLOUT']
  if decision && decision['source'] == Optimizely::DecisionService::DECISION_SOURCES['FEATURE_TEST']
    source_info = {
      experiment_key: decision.experiment['key'],
      variation_key: variation['key']
    }
    source_string = Optimizely::DecisionService::DECISION_SOURCES['FEATURE_TEST']
  end

  @notification_center.send_notifications(
    NotificationCenter::NOTIFICATION_TYPES[:DECISION],
    Helpers::Constants::DECISION_NOTIFICATION_TYPES['FEATURE_VARIABLE'], user_id, (attributes || {}),
    feature_key: feature_flag_key,
    feature_enabled: feature_enabled,
    source: source_string,
    variable_key: variable_key,
    variable_type: variable_type,
    variable_value: variable_value,
    source_info: source_info || {}
  )

  variable_value
end
get_feature_variable_for_variation(feature_flag_key, feature_enabled, variation, variable, user_id) click to toggle source
# File lib/optimizely.rb, line 981
def get_feature_variable_for_variation(feature_flag_key, feature_enabled, variation, variable, user_id)
  # Helper method to get the non type-casted value for a variable attached to a
  # feature flag. Returns appropriate variable value depending on whether there
  # was a matching variation, feature was enabled or not or varible was part of the
  # available variation or not. Also logs the appropriate message explaining how it
  # evaluated the value of the variable.
  #
  # feature_flag_key - String key of feature flag the variable belongs to
  # feature_enabled - Boolean indicating if feature is enabled or not
  # variation - varition returned by decision service
  # user_id - String user ID
  #
  # Returns string value of the variable.

  config = project_config
  variable_value = variable['defaultValue']
  if variation
    if feature_enabled == true
      variation_variable_usages = config.variation_id_to_variable_usage_map[variation['id']]
      variable_id = variable['id']
      if variation_variable_usages&.key?(variable_id)
        variable_value = variation_variable_usages[variable_id]['value']
        @logger.log(Logger::INFO,
                    "Got variable value '#{variable_value}' for variable '#{variable['key']}' of feature flag '#{feature_flag_key}'.")
      else
        @logger.log(Logger::DEBUG,
                    "Variable value is not defined. Returning the default variable value '#{variable_value}' for variable '#{variable['key']}'.")

      end
    else
      @logger.log(Logger::DEBUG,
                  "Feature '#{feature_flag_key}' is not enabled for user '#{user_id}'. Returning the default variable value '#{variable_value}'.")
    end
  else
    @logger.log(Logger::INFO,
                "User '#{user_id}' was not bucketed into experiment or rollout for feature flag '#{feature_flag_key}'. Returning the default variable value '#{variable_value}'.")
  end
  variable_value
end
get_variation_with_config(experiment_key, user_id, attributes, config) click to toggle source
# File lib/optimizely.rb, line 867
def get_variation_with_config(experiment_key, user_id, attributes, config)
  # 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.
  # config - Instance of DatfileProjectConfig
  #
  # 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.
  experiment = config.get_experiment_from_key(experiment_key)
  return nil if experiment.nil?

  experiment_id = experiment['id']

  return nil unless user_inputs_valid?(attributes)

  variation_id, = @decision_service.get_variation(config, experiment_id, user_id, attributes)
  variation = config.get_variation_from_id(experiment_key, variation_id) unless variation_id.nil?
  variation_key = variation['key'] if variation
  decision_notification_type = if config.feature_experiment?(experiment_id)
                                 Helpers::Constants::DECISION_NOTIFICATION_TYPES['FEATURE_TEST']
                               else
                                 Helpers::Constants::DECISION_NOTIFICATION_TYPES['AB_TEST']
                               end
  @notification_center.send_notifications(
    NotificationCenter::NOTIFICATION_TYPES[:DECISION],
    decision_notification_type, user_id, (attributes || {}),
    experiment_key: experiment_key,
    variation_key: variation_key
  )

  variation_key
end
project_config() click to toggle source
# File lib/optimizely.rb, line 1113
def project_config
  @config_manager.config
end
send_impression(config, experiment, variation_key, flag_key, rule_key, enabled, rule_type, user_id, attributes = nil) click to toggle source
# File lib/optimizely.rb, line 1068
def send_impression(config, experiment, variation_key, flag_key, rule_key, enabled, rule_type, user_id, attributes = nil)
  if experiment.nil?
    experiment = {
      'id' => '',
      'key' => '',
      'layerId' => '',
      'status' => '',
      'variations' => [],
      'trafficAllocation' => [],
      'audienceIds' => [],
      'audienceConditions' => [],
      'forcedVariations' => {}
    }
  end

  experiment_id = experiment['id']
  experiment_key = experiment['key']

  variation_id = ''
  variation_id = config.get_variation_id_from_key_by_experiment_id(experiment_id, variation_key) if experiment_id != ''

  metadata = {
    flag_key: flag_key,
    rule_key: rule_key,
    rule_type: rule_type,
    variation_key: variation_key,
    enabled: enabled
  }

  user_event = UserEventFactory.create_impression_event(config, experiment, variation_id, metadata, user_id, attributes)
  @event_processor.process(user_event)
  return unless @notification_center.notification_count(NotificationCenter::NOTIFICATION_TYPES[:ACTIVATE]).positive?

  @logger.log(Logger::INFO, "Activating user '#{user_id}' in experiment '#{experiment_key}'.")

  experiment = nil if experiment_id == ''
  variation = nil
  variation = config.get_variation_from_id_by_experiment_id(experiment_id, variation_id) unless experiment.nil?
  log_event = EventFactory.create_log_event(user_event, @logger)
  @notification_center.send_notifications(
    NotificationCenter::NOTIFICATION_TYPES[:ACTIVATE],
    experiment, user_id, attributes, variation, log_event
  )
end
user_inputs_valid?(attributes = nil, event_tags = nil) click to toggle source
# File lib/optimizely.rb, line 1021
def user_inputs_valid?(attributes = nil, event_tags = nil)
  # Helper method to validate user inputs.
  #
  # attributes - Dict representing user attributes.
  # event_tags - Dict representing metadata associated with an event.
  #
  # Returns boolean True if inputs are valid. False otherwise.

  return false if !attributes.nil? && !attributes_valid?(attributes)

  return false if !event_tags.nil? && !event_tags_valid?(event_tags)

  true
end
validate_instantiation_options() click to toggle source
# File lib/optimizely.rb, line 1054
def validate_instantiation_options
  raise InvalidInputError, 'logger' unless Helpers::Validator.logger_valid?(@logger)

  unless Helpers::Validator.error_handler_valid?(@error_handler)
    @error_handler = NoOpErrorHandler.new
    raise InvalidInputError, 'error_handler'
  end

  return if Helpers::Validator.event_dispatcher_valid?(@event_dispatcher)

  @event_dispatcher = EventDispatcher.new(logger: @logger, error_handler: @error_handler)
  raise InvalidInputError, 'event_dispatcher'
end