Esempio n. 1
0
        /// <summary>
        /// Get the variation if the user is bucketed for one of the experiments on this feature flag.
        /// </summary>
        /// <param name = "featureFlag" >The feature flag the user wants to access.</param>
        /// <param name = "userId" >User Identifier</param>
        /// <param name = "filteredAttributes" >The user's attributes. This should be filtered to just attributes in the Datafile.</param>
        /// <returns>null if the user is not bucketed into the rollout or if the feature flag was not attached to a rollout.
        /// Otherwise the FeatureDecision entity</returns>
        public virtual Result <FeatureDecision> GetVariationForFeatureExperiment(FeatureFlag featureFlag,
                                                                                 OptimizelyUserContext user,
                                                                                 UserAttributes filteredAttributes,
                                                                                 ProjectConfig config,
                                                                                 OptimizelyDecideOption[] options)
        {
            var reasons = new DecisionReasons();
            var userId  = user.GetUserId();

            if (featureFlag == null)
            {
                Logger.Log(LogLevel.ERROR, "Invalid feature flag provided.");
                return(Result <FeatureDecision> .NullResult(reasons));
            }

            if (featureFlag.ExperimentIds == null || featureFlag.ExperimentIds.Count == 0)
            {
                Logger.Log(LogLevel.INFO, reasons.AddInfo($"The feature flag \"{featureFlag.Key}\" is not used in any experiments."));
                return(Result <FeatureDecision> .NullResult(reasons));
            }

            foreach (var experimentId in featureFlag.ExperimentIds)
            {
                var       experiment        = config.GetExperimentFromId(experimentId);
                Variation decisionVariation = null;

                if (string.IsNullOrEmpty(experiment.Key))
                {
                    continue;
                }

                var forcedDecisionResponse = user.FindValidatedForcedDecision(
                    new OptimizelyDecisionContext(featureFlag.Key, experiment?.Key),
                    config);
                reasons += forcedDecisionResponse.DecisionReasons;

                if (forcedDecisionResponse?.ResultObject != null)
                {
                    decisionVariation = forcedDecisionResponse.ResultObject;
                }
                else
                {
                    var decisionResponse = GetVariation(experiment, user, config, options);

                    reasons          += decisionResponse?.DecisionReasons;
                    decisionVariation = decisionResponse.ResultObject;
                }

                if (!string.IsNullOrEmpty(decisionVariation?.Id))
                {
                    Logger.Log(LogLevel.INFO, reasons.AddInfo($"The user \"{userId}\" is bucketed into experiment \"{experiment.Key}\" of feature \"{featureFlag.Key}\"."));

                    var featureDecision = new FeatureDecision(experiment, decisionVariation, FeatureDecision.DECISION_SOURCE_FEATURE_TEST);
                    return(Result <FeatureDecision> .NewResult(featureDecision, reasons));
                }
            }

            Logger.Log(LogLevel.INFO, reasons.AddInfo($"The user \"{userId}\" is not bucketed into any of the experiments on the feature \"{featureFlag.Key}\"."));
            return(Result <FeatureDecision> .NullResult(reasons));
        }
Esempio n. 2
0
        /// <summary>
        /// Try to bucket the user into a rollout rule.
        /// Evaluate the user for rules in priority order by seeing if the user satisfies the audience.
        /// Fall back onto the everyone else rule if the user is ever excluded from a rule due to traffic allocation.
        /// </summary>
        /// <param name = "featureFlag" >The feature flag the user wants to access.</param>
        /// <param name = "userId" >User Identifier</param>
        /// <param name = "filteredAttributes" >The user's attributes. This should be filtered to just attributes in the Datafile.</param>
        /// <param name = "reasons" >Decision log messages.</param>
        /// <returns>null if the user is not bucketed into the rollout or if the feature flag was not attached to a rollout.
        /// otherwise the FeatureDecision entity</returns>
        public virtual Result <FeatureDecision> GetVariationForFeatureRollout(FeatureFlag featureFlag,
                                                                              OptimizelyUserContext user,
                                                                              ProjectConfig config)
        {
            var reasons = new DecisionReasons();

            if (featureFlag == null)
            {
                Logger.Log(LogLevel.ERROR, "Invalid feature flag provided.");
                return(Result <FeatureDecision> .NullResult(reasons));
            }

            if (string.IsNullOrEmpty(featureFlag.RolloutId))
            {
                Logger.Log(LogLevel.INFO, reasons.AddInfo($"The feature flag \"{featureFlag.Key}\" is not used in a rollout."));
                return(Result <FeatureDecision> .NullResult(reasons));
            }

            Rollout rollout = config.GetRolloutFromId(featureFlag.RolloutId);

            if (string.IsNullOrEmpty(rollout.Id))
            {
                Logger.Log(LogLevel.ERROR, reasons.AddInfo($"The rollout with id \"{featureFlag.RolloutId}\" is not found in the datafile for feature flag \"{featureFlag.Key}\""));
                return(Result <FeatureDecision> .NullResult(reasons));
            }

            if (rollout.Experiments == null || rollout.Experiments.Count == 0)
            {
                return(Result <FeatureDecision> .NullResult(reasons));
            }

            var rolloutRulesLength = rollout.Experiments.Count;
            var rolloutRules       = rollout.Experiments;

            var userId     = user.GetUserId();
            var attributes = user.GetAttributes();

            var index = 0;

            while (index < rolloutRulesLength)
            {
                // To skip rules
                var skipToEveryoneElse = false;

                //Check forced decision first
                var rule                   = rolloutRules[index];
                var decisionContext        = new OptimizelyDecisionContext(featureFlag.Key, rule.Key);
                var forcedDecisionResponse = user.FindValidatedForcedDecision(decisionContext, config);

                reasons += forcedDecisionResponse.DecisionReasons;
                if (forcedDecisionResponse.ResultObject != null)
                {
                    return(Result <FeatureDecision> .NewResult(new FeatureDecision(rule, forcedDecisionResponse.ResultObject, null), reasons));
                }

                // Regular decision

                // Get Bucketing ID from user attributes.
                var bucketingIdResult = GetBucketingId(userId, attributes);
                reasons += bucketingIdResult.DecisionReasons;

                var everyoneElse = index == rolloutRulesLength - 1;

                var loggingKey = everyoneElse ? "Everyone Else" : string.Format("{0}", index + 1);

                // Evaluate if user meets the audience condition of this rollout rule
                var doesUserMeetAudienceConditionsResult = ExperimentUtils.DoesUserMeetAudienceConditions(config, rule, attributes, LOGGING_KEY_TYPE_RULE, rule.Key, Logger);
                reasons += doesUserMeetAudienceConditionsResult.DecisionReasons;
                if (doesUserMeetAudienceConditionsResult.ResultObject)
                {
                    Logger.Log(LogLevel.INFO, reasons.AddInfo($"User \"{userId}\" meets condition for targeting rule \"{loggingKey}\"."));

                    var bucketedVariation = Bucketer.Bucket(config, rule, bucketingIdResult.ResultObject, userId);
                    reasons += bucketedVariation?.DecisionReasons;

                    if (bucketedVariation?.ResultObject?.Key != null)
                    {
                        Logger.Log(LogLevel.INFO, reasons.AddInfo($"User \"{userId}\" is in the traffic group of targeting rule \"{loggingKey}\"."));

                        return(Result <FeatureDecision> .NewResult(new FeatureDecision(rule, bucketedVariation.ResultObject, FeatureDecision.DECISION_SOURCE_ROLLOUT), reasons));
                    }
                    else if (!everyoneElse)
                    {
                        //skip this logging for everyoneElse rule since this has a message not for everyoneElse
                        Logger.Log(LogLevel.INFO, reasons.AddInfo($"User \"{userId}\" is not in the traffic group for targeting rule \"{loggingKey}\". Checking EveryoneElse rule now."));
                        skipToEveryoneElse = true;
                    }
                }
                else
                {
                    Logger.Log(LogLevel.DEBUG, reasons.AddInfo($"User \"{userId}\" does not meet the conditions for targeting rule \"{loggingKey}\"."));
                }

                // the last rule is special for "Everyone Else"
                index = skipToEveryoneElse ? (rolloutRulesLength - 1) : (index + 1);
            }

            return(Result <FeatureDecision> .NullResult(reasons));
        }