/// <summary> /// Gets Map of all FeatureFlags and associated experiment map inside it /// </summary> /// <param name="projectConfig">The project config</param> /// <param name="experimentsMapById">Dictionary of experiment Id as key and value as experiment object</param> /// <returns>Dictionary | Dictionary of FeatureFlag key and value as OptimizelyFeature object</returns> private IDictionary <string, OptimizelyFeature> GetFeaturesMap(ProjectConfig projectConfig, IDictionary <string, OptimizelyExperiment> experimentsMapById) { var FeaturesMap = new Dictionary <string, OptimizelyFeature>(); foreach (var featureFlag in projectConfig.FeatureFlags) { var experimentRules = featureFlag.ExperimentIds.Select(experimentId => experimentsMapById[experimentId]).ToList(); var featureVariableMap = featureFlag.Variables.Select(v => (OptimizelyVariable)v).ToDictionary(k => k.Key, v => v) ?? new Dictionary <string, OptimizelyVariable>(); var featureExperimentMap = experimentRules.ToDictionary(experiment => experiment.Key, experiment => experiment); var rollout = projectConfig.GetRolloutFromId(featureFlag.RolloutId); var deliveryRules = GetDeliveryRules(featureFlag.Id, rollout.Experiments, projectConfig); var optimizelyFeature = new OptimizelyFeature(featureFlag.Id, featureFlag.Key, experimentRules, deliveryRules, featureExperimentMap, featureVariableMap); FeaturesMap.Add(featureFlag.Key, optimizelyFeature); } return(FeaturesMap); }
public void TestBucketRolloutRule() { var bucketer = new Bucketer(LoggerMock.Object); var rollout = Config.GetRolloutFromId("166660"); var rolloutRule = rollout.Experiments[1]; var expectedVariation = Config.GetVariationFromId(rolloutRule.Key, "177773"); Assert.True(TestData.CompareObjects(expectedVariation, bucketer.Bucket(Config, rolloutRule, "testBucketingId", TestUserId))); }
public void TestGetVariationForFeatureRolloutWhenUserIsBucketedInTheTargetingRule() { var featureFlag = ProjectConfig.GetFeatureFlagFromKey("boolean_single_variable_feature"); var rolloutId = featureFlag.RolloutId; var rollout = ProjectConfig.GetRolloutFromId(rolloutId); var experiment = rollout.Experiments[0]; var variation = experiment.Variations[0]; var expectedDecision = new FeatureDecision(experiment, variation, FeatureDecision.DECISION_SOURCE_ROLLOUT); var userAttributes = new UserAttributes { { "browser_type", "chrome" } }; BucketerMock.Setup(bm => bm.Bucket(It.IsAny <ProjectConfig>(), It.IsAny <Experiment>(), It.IsAny <string>(), It.IsAny <string>())).Returns(variation); var decisionService = new DecisionService(BucketerMock.Object, ErrorHandlerMock.Object, ProjectConfig, null, LoggerMock.Object); var actualDecision = decisionService.GetVariationForFeatureRollout(featureFlag, "user_1", userAttributes); Assert.IsTrue(TestData.CompareObjects(expectedDecision, actualDecision)); }
public void TestBucketRolloutRule() { var bucketer = new Bucketer(LoggerMock.Object); var rollout = Config.GetRolloutFromId("166660"); var rolloutRule = rollout.Experiments[1]; var expectedVariation = Config.GetVariationFromId(rolloutRule.Key, "177773"); var variationResult = bucketer.Bucket(Config, rolloutRule, "testBucketingId", TestUserId); Assert.True(TestData.CompareObjects(expectedVariation, variationResult.ResultObject)); Assert.AreEqual(variationResult.DecisionReasons.ToReport().Count, 0); var variationsResult = bucketer.Bucket(Config, rolloutRule, "testBucketingId", TestUserId); Assert.True(TestData.CompareObjects(expectedVariation, variationsResult.ResultObject)); Assert.AreEqual(variationsResult.DecisionReasons.ToReport(true).Count, 1); Assert.AreEqual(variationsResult.DecisionReasons.ToReport(true)[0], "User [testUserId] is in variation [177773] of experiment [177772]."); }
/// <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> /// <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 FeatureDecision GetVariationForFeatureRollout(FeatureFlag featureFlag, string userId, UserAttributes filteredAttributes, ProjectConfig config) { if (featureFlag == null) { Logger.Log(LogLevel.ERROR, "Invalid feature flag provided."); return(null); } if (string.IsNullOrEmpty(featureFlag.RolloutId)) { Logger.Log(LogLevel.INFO, $"The feature flag \"{featureFlag.Key}\" is not used in a rollout."); return(null); } Rollout rollout = config.GetRolloutFromId(featureFlag.RolloutId); if (string.IsNullOrEmpty(rollout.Id)) { Logger.Log(LogLevel.ERROR, $"The rollout with id \"{featureFlag.RolloutId}\" is not found in the datafile for feature flag \"{featureFlag.Key}\""); return(null); } if (rollout.Experiments == null || rollout.Experiments.Count == 0) { return(null); } Variation variation = null; var rolloutRulesLength = rollout.Experiments.Count; // Get Bucketing ID from user attributes. string bucketingId = GetBucketingId(userId, filteredAttributes); // For all rules before the everyone else rule for (int i = 0; i < rolloutRulesLength - 1; i++) { var rolloutRule = rollout.Experiments[i]; if (ExperimentUtils.IsUserInExperiment(config, rolloutRule, filteredAttributes, Logger)) { variation = Bucketer.Bucket(config, rolloutRule, bucketingId, userId); if (variation == null || string.IsNullOrEmpty(variation.Id)) { break; } return(new FeatureDecision(rolloutRule, variation, FeatureDecision.DECISION_SOURCE_ROLLOUT)); } else { var audience = config.GetAudience(rolloutRule.AudienceIds[0]); Logger.Log(LogLevel.DEBUG, $"User \"{userId}\" does not meet the conditions to be in rollout rule for audience \"{audience.Name}\"."); } } // Get the last rule which is everyone else rule. var everyoneElseRolloutRule = rollout.Experiments[rolloutRulesLength - 1]; if (ExperimentUtils.IsUserInExperiment(config, everyoneElseRolloutRule, filteredAttributes, Logger)) { variation = Bucketer.Bucket(config, everyoneElseRolloutRule, bucketingId, userId); if (variation != null && !string.IsNullOrEmpty(variation.Id)) { return(new FeatureDecision(everyoneElseRolloutRule, variation, FeatureDecision.DECISION_SOURCE_ROLLOUT)); } } else { var audience = config.GetAudience(everyoneElseRolloutRule.AudienceIds[0]); Logger.Log(LogLevel.DEBUG, $"User \"{userId}\" does not meet the conditions to be in rollout rule for audience \"{audience.Name}\"."); } return(null); }
public void TestInit() { // Check Version Assert.AreEqual("4", Config.Version); // Check Account ID Assert.AreEqual("1592310167", Config.AccountId); // Check Project ID Assert.AreEqual("7720880029", Config.ProjectId); // Check Revision Assert.AreEqual("15", Config.Revision); // Check Group ID Map var expectedGroupId = CreateDictionary("7722400015", Config.GetGroup("7722400015")); var actual = Config.GroupIdMap; Assert.IsTrue(TestData.CompareObjects(expectedGroupId, actual)); // Check Experiment Key Map var experimentKeyMap = new Dictionary <string, object>() { { "test_experiment", Config.GetExperimentFromKey("test_experiment") }, { "paused_experiment", Config.GetExperimentFromKey("paused_experiment") }, { "test_experiment_multivariate", Config.GetExperimentFromKey("test_experiment_multivariate") }, { "test_experiment_with_feature_rollout", Config.GetExperimentFromKey("test_experiment_with_feature_rollout") }, { "test_experiment_double_feature", Config.GetExperimentFromKey("test_experiment_double_feature") }, { "test_experiment_integer_feature", Config.GetExperimentFromKey("test_experiment_integer_feature") }, { "group_experiment_1", Config.GetExperimentFromKey("group_experiment_1") }, { "group_experiment_2", Config.GetExperimentFromKey("group_experiment_2") }, { "etag1", Config.GetExperimentFromKey("etag1") }, { "etag2", Config.GetExperimentFromKey("etag2") }, { "etag3", Config.GetExperimentFromKey("etag3") }, { "etag4", Config.GetExperimentFromKey("etag4") } }; Assert.IsTrue(TestData.CompareObjects(experimentKeyMap, Config.ExperimentKeyMap)); // Check Experiment ID Map var experimentIdMap = new Dictionary <string, object>() { { "7716830082", Config.GetExperimentFromId("7716830082") }, { "7716830585", Config.GetExperimentFromId("7716830585") }, { "122230", Config.GetExperimentFromId("122230") }, { "122235", Config.GetExperimentFromId("122235") }, { "122238", Config.GetExperimentFromId("122238") }, { "122241", Config.GetExperimentFromId("122241") }, { "7723330021", Config.GetExperimentFromId("7723330021") }, { "7718750065", Config.GetExperimentFromId("7718750065") }, { "223", Config.GetExperimentFromId("223") }, { "118", Config.GetExperimentFromId("118") }, { "224", Config.GetExperimentFromId("224") }, { "119", Config.GetExperimentFromId("119") } }; Assert.IsTrue(TestData.CompareObjects(experimentIdMap, Config.ExperimentIdMap)); // Check Event key Map var eventKeyMap = new Dictionary <string, object> { { "purchase", Config.GetEvent("purchase") } }; Assert.IsTrue(TestData.CompareObjects(eventKeyMap, Config.EventKeyMap)); // Check Attribute Key Map var attributeKeyMap = new Dictionary <string, object> { { "device_type", Config.GetAttribute("device_type") }, { "location", Config.GetAttribute("location") }, { "browser_type", Config.GetAttribute("browser_type") }, { "boolean_key", Config.GetAttribute("boolean_key") }, { "integer_key", Config.GetAttribute("integer_key") }, { "double_key", Config.GetAttribute("double_key") } }; Assert.IsTrue(TestData.CompareObjects(attributeKeyMap, Config.AttributeKeyMap)); // Check Audience ID Map var audienceIdMap = new Dictionary <string, object> { { "7718080042", Config.GetAudience("7718080042") }, { "11154", Config.GetAudience("11154") }, { "100", Config.GetAudience("100") } }; Assert.IsTrue(TestData.CompareObjects(audienceIdMap, Config.AudienceIdMap)); // Check Variation Key Map var expectedVariationKeyMap = new Dictionary <string, object> { { "test_experiment", new Dictionary <string, object> { { "control", Config.GetVariationFromKey("test_experiment", "control") }, { "variation", Config.GetVariationFromKey("test_experiment", "variation") } } }, { "paused_experiment", new Dictionary <string, object> { { "control", Config.GetVariationFromKey("paused_experiment", "control") }, { "variation", Config.GetVariationFromKey("paused_experiment", "variation") } } }, { "group_experiment_1", new Dictionary <string, object> { { "group_exp_1_var_1", Config.GetVariationFromKey("group_experiment_1", "group_exp_1_var_1") }, { "group_exp_1_var_2", Config.GetVariationFromKey("group_experiment_1", "group_exp_1_var_2") } } }, { "group_experiment_2", new Dictionary <string, object> { { "group_exp_2_var_1", Config.GetVariationFromKey("group_experiment_2", "group_exp_2_var_1") }, { "group_exp_2_var_2", Config.GetVariationFromKey("group_experiment_2", "group_exp_2_var_2") } } }, { "test_experiment_multivariate", new Dictionary <string, object> { { "Fred", Config.GetVariationFromKey("test_experiment_multivariate", "Fred") }, { "Feorge", Config.GetVariationFromKey("test_experiment_multivariate", "Feorge") }, { "Gred", Config.GetVariationFromKey("test_experiment_multivariate", "Gred") }, { "George", Config.GetVariationFromKey("test_experiment_multivariate", "George") } } }, { "test_experiment_with_feature_rollout", new Dictionary <string, object> { { "control", Config.GetVariationFromKey("test_experiment_with_feature_rollout", "control") }, { "variation", Config.GetVariationFromKey("test_experiment_with_feature_rollout", "variation") } } }, { "test_experiment_double_feature", new Dictionary <string, object> { { "control", Config.GetVariationFromKey("test_experiment_double_feature", "control") }, { "variation", Config.GetVariationFromKey("test_experiment_double_feature", "variation") } } }, { "test_experiment_integer_feature", new Dictionary <string, object> { { "control", Config.GetVariationFromKey("test_experiment_integer_feature", "control") }, { "variation", Config.GetVariationFromKey("test_experiment_integer_feature", "variation") } } }, { "177770", new Dictionary <string, object> { { "177771", Config.GetVariationFromKey("177770", "177771") } } }, { "177772", new Dictionary <string, object> { { "177773", Config.GetVariationFromKey("177772", "177773") } } }, { "177776", new Dictionary <string, object> { { "177778", Config.GetVariationFromKey("177776", "177778") } } }, { "177774", new Dictionary <string, object> { { "177775", Config.GetVariationFromKey("177774", "177775") } } }, { "177779", new Dictionary <string, object> { { "177780", Config.GetVariationFromKey("177779", "177780") } } }, { "177781", new Dictionary <string, object> { { "177782", Config.GetVariationFromKey("177781", "177782") } } }, { "177783", new Dictionary <string, object> { { "177784", Config.GetVariationFromKey("177783", "177784") } } }, { "188880", new Dictionary <string, object> { { "188881", Config.GetVariationFromKey("188880", "188881") } } }, { "etag1", new Dictionary <string, object> { { "vtag1", Config.GetVariationFromKey("etag1", "vtag1") }, { "vtag2", Config.GetVariationFromKey("etag1", "vtag2") } } }, { "etag2", new Dictionary <string, object> { { "vtag3", Config.GetVariationFromKey("etag2", "vtag3") }, { "vtag4", Config.GetVariationFromKey("etag2", "vtag4") } } }, { "etag3", new Dictionary <string, object> { { "vtag5", Config.GetVariationFromKey("etag3", "vtag5") }, { "vtag6", Config.GetVariationFromKey("etag3", "vtag6") } } }, { "etag4", new Dictionary <string, object> { { "vtag7", Config.GetVariationFromKey("etag4", "vtag7") }, { "vtag8", Config.GetVariationFromKey("etag4", "vtag8") } } } }; Assert.IsTrue(TestData.CompareObjects(expectedVariationKeyMap, Config.VariationKeyMap)); // Check Variation ID Map var expectedVariationIdMap = new Dictionary <string, object> { { "test_experiment", new Dictionary <string, object> { { "7722370027", Config.GetVariationFromId("test_experiment", "7722370027") }, { "7721010009", Config.GetVariationFromId("test_experiment", "7721010009") } } }, { "paused_experiment", new Dictionary <string, object> { { "7722370427", Config.GetVariationFromId("paused_experiment", "7722370427") }, { "7721010509", Config.GetVariationFromId("paused_experiment", "7721010509") } } }, { "test_experiment_multivariate", new Dictionary <string, object> { { "122231", Config.GetVariationFromId("test_experiment_multivariate", "122231") }, { "122232", Config.GetVariationFromId("test_experiment_multivariate", "122232") }, { "122233", Config.GetVariationFromId("test_experiment_multivariate", "122233") }, { "122234", Config.GetVariationFromId("test_experiment_multivariate", "122234") } } }, { "test_experiment_with_feature_rollout", new Dictionary <string, object> { { "122236", Config.GetVariationFromId("test_experiment_with_feature_rollout", "122236") }, { "122237", Config.GetVariationFromId("test_experiment_with_feature_rollout", "122237") } } }, { "test_experiment_double_feature", new Dictionary <string, object> { { "122239", Config.GetVariationFromId("test_experiment_double_feature", "122239") }, { "122240", Config.GetVariationFromId("test_experiment_double_feature", "122240") } } }, { "test_experiment_integer_feature", new Dictionary <string, object> { { "122242", Config.GetVariationFromId("test_experiment_integer_feature", "122242") }, { "122243", Config.GetVariationFromId("test_experiment_integer_feature", "122243") } } }, { "group_experiment_1", new Dictionary <string, object> { { "7722260071", Config.GetVariationFromId("group_experiment_1", "7722260071") }, { "7722360022", Config.GetVariationFromId("group_experiment_1", "7722360022") } } }, { "group_experiment_2", new Dictionary <string, object> { { "7713030086", Config.GetVariationFromId("group_experiment_2", "7713030086") }, { "7725250007", Config.GetVariationFromId("group_experiment_2", "7725250007") } } }, { "177770", new Dictionary <string, object> { { "177771", Config.GetVariationFromId("177770", "177771") } } }, { "177772", new Dictionary <string, object> { { "177773", Config.GetVariationFromId("177772", "177773") } } }, { "177776", new Dictionary <string, object> { { "177778", Config.GetVariationFromId("177776", "177778") } } }, { "177774", new Dictionary <string, object> { { "177775", Config.GetVariationFromId("177774", "177775") } } }, { "177779", new Dictionary <string, object> { { "177780", Config.GetVariationFromId("177779", "177780") } } }, { "177781", new Dictionary <string, object> { { "177782", Config.GetVariationFromId("177781", "177782") } } }, { "177783", new Dictionary <string, object> { { "177784", Config.GetVariationFromId("177783", "177784") } } }, { "188880", new Dictionary <string, object> { { "188881", Config.GetVariationFromId("188880", "188881") } } }, { "etag1", new Dictionary <string, object> { { "276", Config.GetVariationFromId("etag1", "276") }, { "277", Config.GetVariationFromId("etag1", "277") } } }, { "etag2", new Dictionary <string, object> { { "278", Config.GetVariationFromId("etag2", "278") }, { "279", Config.GetVariationFromId("etag2", "279") } } }, { "etag3", new Dictionary <string, object> { { "280", Config.GetVariationFromId("etag3", "280") }, { "281", Config.GetVariationFromId("etag3", "281") } } }, { "etag4", new Dictionary <string, object> { { "282", Config.GetVariationFromId("etag4", "282") }, { "283", Config.GetVariationFromId("etag4", "283") } } } }; Assert.IsTrue(TestData.CompareObjects(expectedVariationIdMap, Config.VariationIdMap)); // Check Variation returns correct variable usage var featureVariableUsageInstance = new List <FeatureVariableUsage> { new FeatureVariableUsage { Id = "155560", Value = "F" }, new FeatureVariableUsage { Id = "155561", Value = "red" }, }; var expectedVariationUsage = new Variation { Id = "122231", Key = "Fred", FeatureVariableUsageInstances = featureVariableUsageInstance, FeatureEnabled = true }; var actualVariationUsage = Config.GetVariationFromKey("test_experiment_multivariate", "Fred"); Assert.IsTrue(TestData.CompareObjects(expectedVariationUsage, actualVariationUsage)); // Check Feature Key map. var expectedFeatureKeyMap = new Dictionary <string, FeatureFlag> { { "boolean_feature", Config.GetFeatureFlagFromKey("boolean_feature") }, { "double_single_variable_feature", Config.GetFeatureFlagFromKey("double_single_variable_feature") }, { "integer_single_variable_feature", Config.GetFeatureFlagFromKey("integer_single_variable_feature") }, { "boolean_single_variable_feature", Config.GetFeatureFlagFromKey("boolean_single_variable_feature") }, { "string_single_variable_feature", Config.GetFeatureFlagFromKey("string_single_variable_feature") }, { "multi_variate_feature", Config.GetFeatureFlagFromKey("multi_variate_feature") }, { "mutex_group_feature", Config.GetFeatureFlagFromKey("mutex_group_feature") }, { "empty_feature", Config.GetFeatureFlagFromKey("empty_feature") }, { "no_rollout_experiment_feature", Config.GetFeatureFlagFromKey("no_rollout_experiment_feature") } }; Assert.IsTrue(TestData.CompareObjects(expectedFeatureKeyMap, Config.FeatureKeyMap)); // Check Feature Key map. var expectedRolloutIdMap = new Dictionary <string, Rollout> { { "166660", Config.GetRolloutFromId("166660") }, { "166661", Config.GetRolloutFromId("166661") } }; Assert.IsTrue(TestData.CompareObjects(expectedRolloutIdMap, Config.RolloutIdMap)); }
/// <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 = ValidatedForcedDecision(decisionContext, config, user); 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)); }
/// <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, string userId, UserAttributes filteredAttributes, 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)); } Result <Variation> variationResult = null; var rolloutRulesLength = rollout.Experiments.Count; // Get Bucketing ID from user attributes. var bucketingIdResult = GetBucketingId(userId, filteredAttributes); reasons += bucketingIdResult.DecisionReasons; // For all rules before the everyone else rule for (int i = 0; i < rolloutRulesLength - 1; i++) { string loggingKey = (i + 1).ToString(); var rolloutRule = rollout.Experiments[i]; var userMeetConditionsResult = ExperimentUtils.DoesUserMeetAudienceConditions(config, rolloutRule, filteredAttributes, LOGGING_KEY_TYPE_RULE, loggingKey, Logger); reasons += userMeetConditionsResult.DecisionReasons; if (userMeetConditionsResult.ResultObject) { variationResult = Bucketer.Bucket(config, rolloutRule, bucketingIdResult.ResultObject, userId); reasons += variationResult?.DecisionReasons; if (string.IsNullOrEmpty(variationResult.ResultObject?.Id)) { break; } return(Result <FeatureDecision> .NewResult(new FeatureDecision(rolloutRule, variationResult.ResultObject, FeatureDecision.DECISION_SOURCE_ROLLOUT), reasons)); } else { Logger.Log(LogLevel.DEBUG, $"User \"{userId}\" does not meet the conditions for targeting rule \"{loggingKey}\"."); } } // Get the last rule which is everyone else rule. var everyoneElseRolloutRule = rollout.Experiments[rolloutRulesLength - 1]; var userMeetConditionsResultEveryoneElse = ExperimentUtils.DoesUserMeetAudienceConditions(config, everyoneElseRolloutRule, filteredAttributes, LOGGING_KEY_TYPE_RULE, "Everyone Else", Logger); reasons += userMeetConditionsResultEveryoneElse.DecisionReasons; if (userMeetConditionsResultEveryoneElse.ResultObject) { variationResult = Bucketer.Bucket(config, everyoneElseRolloutRule, bucketingIdResult.ResultObject, userId); reasons += variationResult?.DecisionReasons; if (!string.IsNullOrEmpty(variationResult?.ResultObject?.Id)) { Logger.Log(LogLevel.DEBUG, $"User \"{userId}\" meets conditions for targeting rule \"Everyone Else\"."); return(Result <FeatureDecision> .NewResult(new FeatureDecision(everyoneElseRolloutRule, variationResult.ResultObject, FeatureDecision.DECISION_SOURCE_ROLLOUT), reasons)); } } else { var audience = config.GetAudience(everyoneElseRolloutRule.AudienceIds[0]); Logger.Log(LogLevel.DEBUG, $"User \"{userId}\" does not meet the conditions to be in rollout rule for audience \"{audience.Name}\"."); } return(Result <FeatureDecision> .NewResult(null, reasons)); }