Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 22 additions & 5 deletions lib/optimizely.rb
Original file line number Diff line number Diff line change
Expand Up @@ -220,8 +220,16 @@ def create_optimizely_decision(user_context, flag_key, decision, reasons, decide
decision_source = decision.source
end

if !decide_options.include?(OptimizelyDecideOption::DISABLE_DECISION_EVENT) && (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&.cmab_uuid)
# For holdout decisions, ensure campaign_id is empty string, not nil
campaign_id = nil
if decision_source == Optimizely::DecisionService::DECISION_SOURCES['HOLDOUT']
campaign_id = ''
elsif experiment
campaign_id = experiment['campaignId'] || experiment['layerId']
end

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

Expand Down Expand Up @@ -1271,16 +1279,25 @@ def send_impression(config, experiment, variation_key, flag_key, rule_key, enabl
variation_id = variation ? variation['id'] : ''
end

# For holdout decisions, filter attributes to only include $opt_bot_filtering
filtered_attributes = attributes
if rule_type == Optimizely::DecisionService::DECISION_SOURCES['HOLDOUT']
bot_filtering = attributes&.dig('$opt_bot_filtering')
filtered_attributes = bot_filtering ? {'$opt_bot_filtering' => bot_filtering} : {}
end

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

# Only include enabled field for non-holdout rule types
metadata[:enabled] = enabled unless rule_type == Optimizely::DecisionService::DECISION_SOURCES['HOLDOUT']
metadata[:cmab_uuid] = cmab_uuid unless cmab_uuid.nil?

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

Expand Down
2 changes: 1 addition & 1 deletion lib/optimizely/config/datafile_project_config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ def initialize(datafile, logger, error_handler)
applicable_holdouts << holdout unless excluded_flag_ids.include?(flag_id)
end

@flag_holdouts_map[key] = applicable_holdouts unless applicable_holdouts.empty?
@flag_holdouts_map[flag_id] = applicable_holdouts unless applicable_holdouts.empty?
end

# Adding Holdout variations in variation id and key maps
Expand Down
26 changes: 19 additions & 7 deletions lib/optimizely/decision_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,13 @@ def get_decision_for_flag(feature_flag, user_context, project_config, decide_opt
# Check holdouts
holdouts = project_config.get_holdouts_for_flag(feature_flag['id'])

holdouts.each do |holdout|
# Sort holdouts: global holdouts (empty includedFlags) should be evaluated first
sorted_holdouts = holdouts.sort_by do |holdout|
included_flags = holdout['includedFlags'] || []
included_flags.empty? ? 0 : 1
end

sorted_holdouts.each do |holdout|
holdout_decision = get_variation_for_holdout(holdout, user_context, project_config)
reasons.push(*holdout_decision.reasons)

Expand Down Expand Up @@ -313,12 +319,18 @@ def get_variations_for_feature_list(project_config, feature_flags, user_context,
decisions = []
feature_flags.each do |feature_flag|
# check if the feature is being experiment on and whether the user is bucketed into the experiment
decision_result = get_variation_for_feature_experiment(project_config, feature_flag, user_context, user_profile_tracker, decide_options)
# Only process rollout if no experiment decision was found and no error
if decision_result.decision.nil? && !decision_result.error
decision_result_rollout = get_variation_for_feature_rollout(project_config, feature_flag, user_context) unless decision_result.decision
decision_result.decision = decision_result_rollout.decision
decision_result.reasons.push(*decision_result_rollout.reasons)
holdouts = project_config.get_holdouts_for_flag(feature_flag['id'])

if holdouts && !holdouts.empty?
decision_result = get_decision_for_flag(feature_flag, user_context, project_config, decide_options, user_profile_tracker)
else
decision_result = get_variation_for_feature_experiment(project_config, feature_flag, user_context, user_profile_tracker, decide_options)
# Only process rollout if no experiment decision was found and no error
if decision_result.decision.nil? && !decision_result.error
decision_result_rollout = get_variation_for_feature_rollout(project_config, feature_flag, user_context) unless decision_result.decision
decision_result.decision = decision_result_rollout.decision
decision_result.reasons.push(*decision_result_rollout.reasons)
end
end
decisions << decision_result
end
Expand Down
12 changes: 10 additions & 2 deletions lib/optimizely/event_builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -171,14 +171,22 @@ def get_impression_params(project_config, experiment, variation_id)
experiment_key = experiment['key']
experiment_id = experiment['id']

campaign_id = project_config.experiment_key_map[experiment_key]['layerId'] || project_config.experiment_key_map[experiment_key]['campaignId']
if decision_source == Optimizely::DecisionService::DECISION_SOURCES['HOLDOUT']
campaign_id = ''
entity_id = ''
else
entity_id = campaign_id
end

{
decisions: [{
campaign_id: project_config.experiment_key_map[experiment_key]['layerId'],
campaign_id: campaign_id,
experiment_id: experiment_id,
variation_id: variation_id
}],
events: [{
entity_id: project_config.experiment_key_map[experiment_key]['layerId'],
entity_id: entity_id,
timestamp: create_timestamp,
key: ACTIVATE_EVENT_KEY,
uuid: create_uuid
Expand Down
Loading