Skip to content

Commit c778d08

Browse files
authored
feat(ForcedDecisions): add forced-decisions APIs to OptimizelyUserContext (#287)
Add a set of new APIs for forced-decisions to OptimizelyUserContext: SetForcedDecision GetForcedDecision RemoveForcedDecision RemoveAllForcedDecisions
1 parent a49bc5b commit c778d08

10 files changed

+1260
-137
lines changed

lib/optimizely.rb

+27-8
Original file line numberDiff line numberDiff line change
@@ -199,13 +199,20 @@ def decide(user_context, key, decide_options = [])
199199
experiment = nil
200200
decision_source = Optimizely::DecisionService::DECISION_SOURCES['ROLLOUT']
201201

202-
decision, reasons_received = @decision_service.get_variation_for_feature(config, feature_flag, user_id, attributes, decide_options)
202+
variation, reasons_received = user_context.find_validated_forced_decision(key, nil)
203203
reasons.push(*reasons_received)
204204

205+
if variation
206+
decision = Optimizely::DecisionService::Decision.new(nil, variation, Optimizely::DecisionService::DECISION_SOURCES['FEATURE_TEST'])
207+
else
208+
decision, reasons_received = @decision_service.get_variation_for_feature(config, feature_flag, user_context, decide_options)
209+
reasons.push(*reasons_received)
210+
end
211+
205212
# Send impression event if Decision came from a feature test and decide options doesn't include disableDecisionEvent
206213
if decision.is_a?(Optimizely::DecisionService::Decision)
207214
experiment = decision.experiment
208-
rule_key = experiment['key']
215+
rule_key = experiment ? experiment['key'] : nil
209216
variation = decision['variation']
210217
variation_key = variation['key']
211218
feature_enabled = variation['featureEnabled']
@@ -291,6 +298,10 @@ def decide_for_keys(user_context, keys, decide_options = [])
291298
decisions
292299
end
293300

301+
def get_flag_variation_by_key(flag_key, variation_key)
302+
project_config.get_variation_from_flag(flag_key, variation_key)
303+
end
304+
294305
# Buckets visitor and sends impression event to Optimizely.
295306
#
296307
# @param experiment_key - Experiment which needs to be activated.
@@ -490,7 +501,8 @@ def is_feature_enabled(feature_flag_key, user_id, attributes = nil)
490501
return false
491502
end
492503

493-
decision, = @decision_service.get_variation_for_feature(config, feature_flag, user_id, attributes)
504+
user_context = create_user_context(user_id, attributes)
505+
decision, = @decision_service.get_variation_for_feature(config, feature_flag, user_context)
494506

495507
feature_enabled = false
496508
source_string = Optimizely::DecisionService::DECISION_SOURCES['ROLLOUT']
@@ -739,7 +751,8 @@ def get_all_feature_variables(feature_flag_key, user_id, attributes = nil)
739751
return nil
740752
end
741753

742-
decision, = @decision_service.get_variation_for_feature(config, feature_flag, user_id, attributes)
754+
user_context = create_user_context(user_id, attributes)
755+
decision, = @decision_service.get_variation_for_feature(config, feature_flag, user_context)
743756
variation = decision ? decision['variation'] : nil
744757
feature_enabled = variation ? variation['featureEnabled'] : false
745758
all_variables = {}
@@ -881,7 +894,8 @@ def get_variation_with_config(experiment_key, user_id, attributes, config)
881894

882895
return nil unless user_inputs_valid?(attributes)
883896

884-
variation_id, = @decision_service.get_variation(config, experiment_id, user_id, attributes)
897+
user_context = create_user_context(user_id, attributes)
898+
variation_id, = @decision_service.get_variation(config, experiment_id, user_context)
885899
variation = config.get_variation_from_id(experiment_key, variation_id) unless variation_id.nil?
886900
variation_key = variation['key'] if variation
887901
decision_notification_type = if config.feature_experiment?(experiment_id)
@@ -947,7 +961,8 @@ def get_feature_variable_for_type(feature_flag_key, variable_key, variable_type,
947961
return nil
948962
end
949963

950-
decision, = @decision_service.get_variation_for_feature(config, feature_flag, user_id, attributes)
964+
user_context = create_user_context(user_id, attributes)
965+
decision, = @decision_service.get_variation_for_feature(config, feature_flag, user_context)
951966
variation = decision ? decision['variation'] : nil
952967
feature_enabled = variation ? variation['featureEnabled'] : false
953968

@@ -1083,8 +1098,12 @@ def send_impression(config, experiment, variation_key, flag_key, rule_key, enabl
10831098
experiment_id = experiment['id']
10841099
experiment_key = experiment['key']
10851100

1086-
variation_id = ''
1087-
variation_id = config.get_variation_id_from_key_by_experiment_id(experiment_id, variation_key) if experiment_id != ''
1101+
if experiment_id != ''
1102+
variation_id = config.get_variation_id_from_key_by_experiment_id(experiment_id, variation_key)
1103+
else
1104+
varaition = get_flag_variation_by_key(flag_key, variation_key)
1105+
variation_id = varaition ? varaition['id'] : ''
1106+
end
10881107

10891108
metadata = {
10901109
flag_key: flag_key,

lib/optimizely/config/datafile_project_config.rb

+44
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ class DatafileProjectConfig < ProjectConfig
6060
attr_reader :variation_key_map
6161
attr_reader :variation_id_map_by_experiment_id
6262
attr_reader :variation_key_map_by_experiment_id
63+
attr_reader :flag_variation_map
6364

6465
def initialize(datafile, logger, error_handler)
6566
# ProjectConfig init method to fetch and set project config data
@@ -123,6 +124,8 @@ def initialize(datafile, logger, error_handler)
123124
@variation_key_map_by_experiment_id = {}
124125
@variation_id_to_variable_usage_map = {}
125126
@variation_id_to_experiment_map = {}
127+
@flag_variation_map = {}
128+
126129
@experiment_id_map.each_value do |exp|
127130
# Excludes experiments from rollouts
128131
variations = exp.fetch('variations')
@@ -138,6 +141,8 @@ def initialize(datafile, logger, error_handler)
138141
exps = rollout.fetch('experiments')
139142
@rollout_experiment_id_map = @rollout_experiment_id_map.merge(generate_key_map(exps, 'id'))
140143
end
144+
145+
@flag_variation_map = generate_feature_variation_map(@feature_flags)
141146
@all_experiments = @experiment_id_map.merge(@rollout_experiment_id_map)
142147
@all_experiments.each do |id, exp|
143148
variations = exp.fetch('variations')
@@ -165,6 +170,24 @@ def initialize(datafile, logger, error_handler)
165170
end
166171
end
167172

173+
def get_rules_for_flag(feature_flag)
174+
# Retrieves rules for a given feature flag
175+
#
176+
# feature_flag - String key representing the feature_flag
177+
#
178+
# Returns rules in feature flag
179+
rules = feature_flag['experimentIds'].map { |exp_id| @experiment_id_map[exp_id] }
180+
rollout = feature_flag['rolloutId'].empty? ? nil : @rollout_id_map[feature_flag['rolloutId']]
181+
182+
if rollout
183+
rollout_experiments = rollout.fetch('experiments')
184+
rollout_experiments.each do |exp|
185+
rules.push(exp)
186+
end
187+
end
188+
rules
189+
end
190+
168191
def self.create(datafile, logger, error_handler, skip_json_validation)
169192
# Looks up and sets datafile and config based on response body.
170193
#
@@ -279,6 +302,13 @@ def get_audience_from_id(audience_id)
279302
nil
280303
end
281304

305+
def get_variation_from_flag(flag_key, variation_key)
306+
variations = @flag_variation_map[flag_key]
307+
return variations.select { |variation| variation['key'] == variation_key }.first if variations
308+
309+
nil
310+
end
311+
282312
def get_variation_from_id(experiment_key, variation_id)
283313
# Get variation given experiment key and variation ID
284314
#
@@ -494,6 +524,20 @@ def rollout_experiment?(experiment_id)
494524

495525
private
496526

527+
def generate_feature_variation_map(feature_flags)
528+
flag_variation_map = {}
529+
feature_flags.each do |flag|
530+
variations = []
531+
get_rules_for_flag(flag).each do |rule|
532+
rule['variations'].each do |rule_variation|
533+
variations.push(rule_variation) if variations.select { |variation| variation['id'] == rule_variation['id'] }.empty?
534+
end
535+
end
536+
flag_variation_map[flag['key']] = variations
537+
end
538+
flag_variation_map
539+
end
540+
497541
def generate_key_map(array, key)
498542
# Helper method to generate map from key to hash in array of hashes
499543
#

0 commit comments

Comments
 (0)