@@ -349,153 +349,94 @@ def get_variation_for_rollout(self, project_config, feature, user):
349349 array of log messages representing decision making.
350350 """
351351 decide_reasons = []
352+ user_id = user .user_id
353+ attributes = user .get_user_attributes ()
352354
353355 if not feature or not feature .rolloutId :
354356 return Decision (None , None , enums .DecisionSources .ROLLOUT ), decide_reasons
355357
356358 rollout = project_config .get_rollout_from_id (feature .rolloutId )
357- rollout_rules = project_config .get_rollout_experiments (rollout )
358359
359- if not rollout or not rollout_rules :
360+ if not rollout :
360361 message = 'There is no rollout of feature {}.' .format (feature .key )
361362 self .logger .debug (message )
362363 decide_reasons .append (message )
363364 return Decision (None , None , enums .DecisionSources .ROLLOUT ), decide_reasons
364365
366+ rollout_rules = project_config .get_rollout_experiments (rollout )
367+
368+ if not rollout_rules :
369+ message = 'Rollout {} has no experiments.' .format (rollout .id )
370+ self .logger .debug (message )
371+ decide_reasons .append (message )
372+ return Decision (None , None , enums .DecisionSources .ROLLOUT ), decide_reasons
373+
365374 index = 0
366375 while index < len (rollout_rules ):
367- decision_response , reasons_received = self .get_variation_from_delivery_rule (project_config ,
368- feature ,
369- rollout_rules , index , user )
376+ skip_to_everyone_else = False
370377
378+ # check forced decision first
379+ rule = rollout_rules [index ]
380+ optimizely_decision_context = OptimizelyUserContext .OptimizelyDecisionContext (feature .key , rule .key )
381+ forced_decision_variation , reasons_received = user .find_validated_forced_decision (
382+ optimizely_decision_context )
371383 decide_reasons += reasons_received
372384
373- variation , skip_to_everyone_else = decision_response
374-
375- if variation :
376- rule = rollout_rules [index ]
377- feature_decision = Decision (experiment = rule , variation = variation ,
378- source = enums .DecisionSources .ROLLOUT )
379-
380- return feature_decision , decide_reasons
381-
382- # the last rule is special for "Everyone Else"
383- index = len (rollout_rules ) - 1 if skip_to_everyone_else else index + 1
384-
385- return Decision (None , None , enums .DecisionSources .ROLLOUT ), decide_reasons
386-
387- def get_variation_from_experiment_rule (self , config , flag_key , rule , user , options ):
388- """ Checks for experiment rule if decision is forced and returns it.
389- Otherwise returns a regular decision.
390-
391- Args:
392- config: Instance of ProjectConfig.
393- flag_key: Key of the flag.
394- rule: Experiment rule.
395- user: ID and attributes for user.
396- options: Decide options.
397-
398- Returns:
399- Decision namedtuple consisting of experiment and variation for the user and
400- array of log messages representing decision making.
401- """
402- decide_reasons = []
403-
404- # check forced decision first
405- optimizely_decision_context = OptimizelyUserContext .OptimizelyDecisionContext (flag_key , rule .key )
406-
407- forced_decision_variation , reasons_received = user .find_validated_forced_decision (
408- optimizely_decision_context )
409- decide_reasons += reasons_received
410-
411- if forced_decision_variation :
412- return forced_decision_variation , decide_reasons
413-
414- # regular decision
415- decision_variation , variation_reasons = self .get_variation (config , rule , user , options )
416- decide_reasons += variation_reasons
417- return decision_variation , decide_reasons
418-
419- def get_variation_from_delivery_rule (self , config , feature , rules , rule_index , user ):
420- """ Checks for delivery rule if decision is forced and returns it.
421- Otherwise returns a regular decision.
385+ if forced_decision_variation :
386+ return Decision (experiment = rule , variation = forced_decision_variation ,
387+ source = enums .DecisionSources .ROLLOUT ), decide_reasons
422388
423- Args:
424- config: Instance of ProjectConfig.
425- flag_key: Key of the flag.
426- rules: Experiment rule.
427- rule_index: integer index of the rule in the list.
428- user: ID and attributes for user.
389+ bucketing_id , bucket_reasons = self ._get_bucketing_id (user_id , attributes )
390+ decide_reasons += bucket_reasons
429391
430- Returns:
431- If forced decision, it returns namedtuple consisting of forced_decision_variation and skip_to_everyone_else
432- and decision reason log messages.
392+ everyone_else = (index == len (rollout_rules ) - 1 )
393+ logging_key = "Everyone Else" if everyone_else else str (index + 1 )
433394
434- If regular decision it returns a tuple of bucketed_variation and skip_to_everyone_else
435- and decision reason log messages
436- """
437- decide_reasons = []
438- skip_to_everyone_else = False
439- bucketed_variation = None
395+ rollout_rule = project_config .get_experiment_from_id (rule .id )
396+ audience_conditions = rollout_rule .get_audience_conditions_or_ids ()
440397
441- # check forced decision first
442- rule = rules [rule_index ]
443- optimizely_decision_context = OptimizelyUserContext .OptimizelyDecisionContext (feature .key , rule .key )
444- forced_decision_variation , reasons_received = user .find_validated_forced_decision (optimizely_decision_context )
398+ audience_decision_response , reasons_received_audience = audience_helper .does_user_meet_audience_conditions (
399+ project_config , audience_conditions , enums .RolloutRuleAudienceEvaluationLogs ,
400+ logging_key , attributes , self .logger )
445401
446- decide_reasons += reasons_received
447-
448- if forced_decision_variation :
449- return (forced_decision_variation , skip_to_everyone_else ), decide_reasons
402+ decide_reasons += reasons_received_audience
450403
451- # regular decision
452- user_id = user .user_id
453- attributes = user .get_user_attributes ()
454-
455- bucketing_id , bucket_reasons = self ._get_bucketing_id (user_id , attributes )
456- decide_reasons += bucket_reasons
457-
458- everyone_else = (rule_index == len (rules ) - 1 )
459- logging_key = "Everyone Else" if everyone_else else str (rule_index + 1 )
460-
461- rollout_rule = config .get_experiment_from_id (rule .id )
462- audience_conditions = rollout_rule .get_audience_conditions_or_ids ()
463-
464- audience_decision_response , reasons_received_audience = audience_helper .does_user_meet_audience_conditions (
465- config , audience_conditions , enums .RolloutRuleAudienceEvaluationLogs , logging_key , attributes , self .logger )
466-
467- decide_reasons += reasons_received_audience
404+ if audience_decision_response :
405+ message = 'User "{}" meets audience conditions for targeting rule {}.' .format (user_id , logging_key )
406+ self .logger .debug (message )
407+ decide_reasons .append (message )
468408
469- if audience_decision_response :
470- message = 'User "{}" meets audience conditions for targeting rule {}.' .format (user_id , logging_key )
471- self .logger .debug (message )
472- decide_reasons .append (message )
409+ bucketed_variation , bucket_reasons = self .bucketer .bucket (project_config , rollout_rule , user_id ,
410+ bucketing_id )
411+ decide_reasons .extend (bucket_reasons )
473412
474- bucketed_variation , bucket_reasons = self .bucketer .bucket (config , rollout_rule , user_id ,
475- bucketing_id )
476- decide_reasons .extend (bucket_reasons )
413+ if bucketed_variation :
414+ message = 'User "{}" bucketed into a targeting rule {}.' .format (user_id , logging_key )
415+ self .logger .debug (message )
416+ decide_reasons .append (message )
417+ return Decision (experiment = rule , variation = bucketed_variation ,
418+ source = enums .DecisionSources .ROLLOUT ), decide_reasons
419+
420+ elif not everyone_else :
421+ # skip this logging for EveryoneElse since this has a message not for everyone_else
422+ message = 'User "{}" not bucketed into a targeting rule {}. ' \
423+ 'Checking "Everyone Else" rule now.' .format (user_id , logging_key )
424+ self .logger .debug (message )
425+ decide_reasons .append (message )
477426
478- if bucketed_variation :
479- message = 'User "{}" bucketed into a targeting rule {}.' .format (user_id , logging_key )
480- self .logger .debug (message )
481- decide_reasons .append (message )
427+ # skip the rest of rollout rules to the everyone-else rule if audience matches but not bucketed.
428+ skip_to_everyone_else = True
482429
483- elif not everyone_else :
484- # skip this logging for EveryoneElse since this has a message not for everyone_else
485- message = 'User "{}" not bucketed into a targeting rule {}. ' \
486- 'Checking "Everyone Else" rule now.' .format (user_id , logging_key )
430+ else :
431+ message = 'User "{}" does not meet audience conditions for targeting rule {}.' .format (
432+ user_id , logging_key )
487433 self .logger .debug (message )
488434 decide_reasons .append (message )
489435
490- # skip the rest of rollout rules to the everyone-else rule if audience matches but not bucketed.
491- skip_to_everyone_else = True
492-
493- else :
494- message = 'User "{}" does not meet audience conditions for targeting rule {}.' .format (user_id , logging_key )
495- self .logger .debug (message )
496- decide_reasons .append (message )
436+ # the last rule is special for "Everyone Else"
437+ index = len (rollout_rules ) - 1 if skip_to_everyone_else else index + 1
497438
498- return ( bucketed_variation , skip_to_everyone_else ), decide_reasons
439+ return Decision ( None , None , enums . DecisionSources . ROLLOUT ), decide_reasons
499440
500441 def get_variation_for_feature (self , project_config , feature , user_context , options = None ):
501442 """ Returns the experiment/variation the user is bucketed in for the given feature.
@@ -517,11 +458,34 @@ def get_variation_for_feature(self, project_config, feature, user_context, optio
517458 # Evaluate each experiment ID and return the first bucketed experiment variation
518459 for experiment in feature .experimentIds :
519460 experiment = project_config .get_experiment_from_id (experiment )
520- if experiment :
521- variation , variation_reasons = self .get_variation_from_experiment_rule (
522- project_config , feature .key , experiment , user_context , options )
523- decide_reasons += variation_reasons
524- if variation :
525- return Decision (experiment , variation , enums .DecisionSources .FEATURE_TEST ), decide_reasons
461+ decision_variation = None
526462
527- return self .get_variation_for_rollout (project_config , feature , user_context )
463+ if experiment :
464+ optimizely_decision_context = OptimizelyUserContext .OptimizelyDecisionContext (feature .key ,
465+ experiment .key )
466+
467+ forced_decision_variation , reasons_received = user_context .find_validated_forced_decision (
468+ optimizely_decision_context )
469+ decide_reasons += reasons_received
470+
471+ if forced_decision_variation :
472+ decision_variation = forced_decision_variation
473+ else :
474+ decision_variation , variation_reasons = self .get_variation (project_config ,
475+ experiment , user_context , options )
476+ decide_reasons += variation_reasons
477+
478+ if decision_variation :
479+ message = 'User "{}" bucketed into a experiment "{}" of feature "{}".' .format (
480+ user_context .user_id , experiment .key , feature .key )
481+ self .logger .debug (message )
482+ return Decision (experiment , decision_variation ,
483+ enums .DecisionSources .FEATURE_TEST ), decide_reasons
484+
485+ message = 'User "{}" is not bucketed into any of the experiments on the feature "{}".' .format (
486+ user_context .user_id , feature .key )
487+ self .logger .debug (message )
488+ variation , rollout_variation_reasons = self .get_variation_for_rollout (project_config , feature , user_context )
489+ if rollout_variation_reasons :
490+ decide_reasons += rollout_variation_reasons
491+ return variation , decide_reasons
0 commit comments