public void TestIsUserInExperimentReturnsFalseIfNoAudienceInORConditionPass() { var experiment = Config.GetExperimentFromKey("feat_with_var_test"); var userAttributes = new UserAttributes { { "house", "Ravenclaw" }, { "lasers", 50 }, { "should_do_it", false } }; Assert.False(ExperimentUtils.IsUserInExperiment(Config, experiment, userAttributes, Logger)); LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Evaluating audiences for experiment ""feat_with_var_test"": [""3468206642"",""3988293898"",""3988293899"",""3468206646"",""3468206647"",""3468206644"",""3468206643""]"), Times.Once); LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Starting to evaluate audience ""3468206642"" with conditions: [""and"", [""or"", [""or"", {""name"": ""house"", ""type"": ""custom_attribute"", ""value"": ""Gryffindor""}]]]"), Times.Once); LoggerMock.Verify(l => l.Log(LogLevel.INFO, @"Audience ""3468206642"" evaluated to FALSE"), Times.Once); LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Starting to evaluate audience ""3988293899"" with conditions: [""and"",[""or"",[""or"",{""name"":""favorite_ice_cream"",""type"":""custom_attribute"",""match"":""exists""}]]]"), Times.Once); LoggerMock.Verify(l => l.Log(LogLevel.INFO, @"Audience ""3988293899"" evaluated to FALSE"), Times.Once); LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Starting to evaluate audience ""3468206646"" with conditions: [""and"",[""or"",[""or"",{""name"":""lasers"",""type"":""custom_attribute"",""match"":""exact"",""value"":45.5}]]]"), Times.Once); LoggerMock.Verify(l => l.Log(LogLevel.INFO, @"Audience ""3468206646"" evaluated to FALSE"), Times.Once); LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Starting to evaluate audience ""3468206647"" with conditions: [""and"",[""or"",[""or"",{""name"":""lasers"",""type"":""custom_attribute"",""match"":""gt"",""value"":70}]]]"), Times.Once); LoggerMock.Verify(l => l.Log(LogLevel.INFO, @"Audience ""3468206647"" evaluated to FALSE"), Times.Once); LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Starting to evaluate audience ""3468206644"" with conditions: [""and"",[""or"",[""or"",{""name"":""lasers"",""type"":""custom_attribute"",""match"":""lt"",""value"":1.0}]]]"), Times.Once); LoggerMock.Verify(l => l.Log(LogLevel.INFO, @"Audience ""3468206644"" evaluated to FALSE"), Times.Once); LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Starting to evaluate audience ""3468206643"" with conditions: [""and"",[""or"",[""or"",{""name"":""should_do_it"",""type"":""custom_attribute"",""match"":""exact"",""value"":true}]]]"), Times.Once); LoggerMock.Verify(l => l.Log(LogLevel.INFO, @"Audience ""3468206643"" evaluated to FALSE"), Times.Once); LoggerMock.Verify(l => l.Log(LogLevel.INFO, @"Audiences for experiment ""feat_with_var_test"" collectively evaluated to FALSE"), Times.Once); }
public void TestIsUserInExperimentReturnsFalseIfAnyAudienceInANDConditionDoesNotPass() { var experiment = Config.GetExperimentFromKey("audience_combinations_experiment"); var userAttributes = new UserAttributes { { "house", "Gryffindor" }, { "lasers", 50 } }; Assert.False(ExperimentUtils.IsUserInExperiment(Config, experiment, userAttributes, Logger)); LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Evaluating audiences for experiment ""audience_combinations_experiment"": [""and"",[""or"",""3468206642"",""3988293898""],[""or"",""3988293899"",""3468206646"",""3468206647"",""3468206644"",""3468206643""]]"), Times.Once); LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Starting to evaluate audience ""3468206642"" with conditions: [""and"", [""or"", [""or"", {""name"": ""house"", ""type"": ""custom_attribute"", ""value"": ""Gryffindor""}]]]"), Times.Once); LoggerMock.Verify(l => l.Log(LogLevel.INFO, @"Audience ""3468206642"" evaluated to TRUE"), Times.Once); LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Starting to evaluate audience ""3988293899"" with conditions: [""and"",[""or"",[""or"",{""name"":""favorite_ice_cream"",""type"":""custom_attribute"",""match"":""exists""}]]]"), Times.Once); LoggerMock.Verify(l => l.Log(LogLevel.INFO, @"Audience ""3988293899"" evaluated to FALSE"), Times.Once); LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Starting to evaluate audience ""3468206646"" with conditions: [""and"",[""or"",[""or"",{""name"":""lasers"",""type"":""custom_attribute"",""match"":""exact"",""value"":45.5}]]]"), Times.Once); LoggerMock.Verify(l => l.Log(LogLevel.INFO, @"Audience ""3468206646"" evaluated to FALSE"), Times.Once); LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Starting to evaluate audience ""3468206647"" with conditions: [""and"",[""or"",[""or"",{""name"":""lasers"",""type"":""custom_attribute"",""match"":""gt"",""value"":70}]]]"), Times.Once); LoggerMock.Verify(l => l.Log(LogLevel.INFO, @"Audience ""3468206647"" evaluated to FALSE"), Times.Once); LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Starting to evaluate audience ""3468206644"" with conditions: [""and"",[""or"",[""or"",{""name"":""lasers"",""type"":""custom_attribute"",""match"":""lt"",""value"":1.0}]]]"), Times.Once); LoggerMock.Verify(l => l.Log(LogLevel.INFO, @"Audience ""3468206644"" evaluated to FALSE"), Times.Once); LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Starting to evaluate audience ""3468206643"" with conditions: [""and"",[""or"",[""or"",{""name"":""should_do_it"",""type"":""custom_attribute"",""match"":""exact"",""value"":true}]]]"), Times.Once); LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Audience condition {""type"":""custom_attribute"",""match"":""exact"",""name"":""should_do_it"",""value"":true} evaluated to UNKNOWN because no value was passed for user attribute ""should_do_it"""), Times.Once); LoggerMock.Verify(l => l.Log(LogLevel.INFO, @"Audience ""3468206643"" evaluated to UNKNOWN"), Times.Once); LoggerMock.Verify(l => l.Log(LogLevel.INFO, @"Audiences for experiment ""audience_combinations_experiment"" collectively evaluated to FALSE"), Times.Once); }
public void TestUserInExperimentAudienceMatch() { var userAttributes = new UserAttributes { { "device_type", "iPhone" }, { "location", "San Francisco" } }; Assert.IsTrue(ExperimentUtils.IsUserInExperiment(Config, Config.GetExperimentFromKey("test_experiment"), userAttributes)); }
public void TestIsUserInExperimentAudienceNoMatch() { var userAttributes = new UserAttributes { { "device_type", "Android" }, { "location", "San Francisco" } }; Assert.IsFalse(ExperimentUtils.IsUserInExperiment(Config, Config.GetExperimentFromKey("test_experiment"), null)); }
public void TestIsUserInExperimentReturnsTrueWithNoAudience() { var experiment = Config.GetExperimentFromKey("feat_with_var_test"); experiment.AudienceIds = new string[] { }; experiment.AudienceConditions = null; Assert.True(ExperimentUtils.IsUserInExperiment(Config, experiment, null, Logger)); LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Evaluating audiences for experiment ""feat_with_var_test"": []"), Times.Once); }
public void TestIsUserInExperimentReturnsTrueIfAllAudiencesInANDConditionPass() { var experiment = Config.GetExperimentFromKey("audience_combinations_experiment"); var userAttributes = new UserAttributes { { "house", "Gryffindor" }, { "favorite_ice_cream", "walls" } }; Assert.True(ExperimentUtils.IsUserInExperiment(Config, experiment, userAttributes, Logger)); }
public void TestIsUserInExperimentReturnsFalseIfAudienceInNOTConditionGetsPassed() { var experiment = Config.GetExperimentFromKey("feat_with_var_test"); experiment.AudienceIds = new string[] { "3468206645" }; var userAttributes = new UserAttributes { { "browser_type", "Chrome" } }; Assert.False(ExperimentUtils.IsUserInExperiment(Config, experiment, userAttributes, Logger)); }
public void TestIsUserInExperimentReturnsTrueWhenAudienceUsedInExperimentNoAttributesProvided() { var experiment = Config.GetExperimentFromKey("feat_with_var_test"); experiment.AudienceIds = new string[] { "3468206648" }; Assert.True(ExperimentUtils.IsUserInExperiment(Config, experiment, null, Logger)); Assert.True(ExperimentUtils.IsUserInExperiment(Config, experiment, new UserAttributes { }, Logger)); LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Evaluating audiences for experiment ""feat_with_var_test"": [""3468206648""]"), Times.Exactly(2)); LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Starting to evaluate audience ""3468206648"" with conditions: [""not"",{""name"":""input_value"",""type"":""custom_attribute"",""match"":""exists""}]"), Times.Exactly(2)); LoggerMock.Verify(l => l.Log(LogLevel.INFO, $@"Audience ""3468206648"" evaluated to TRUE"), Times.Exactly(2)); LoggerMock.Verify(l => l.Log(LogLevel.INFO, @"Audiences for experiment ""feat_with_var_test"" collectively evaluated to TRUE"), Times.Exactly(2)); }
public void TestIsUserInExperimentReturnsTrueIfAnyAudienceInORConditionPass() { var experiment = Config.GetExperimentFromKey("feat_with_var_test"); var userAttributes = new UserAttributes { { "house", "Gryffindor" }, { "lasers", 50 }, { "should_do_it", false } }; Assert.True(ExperimentUtils.IsUserInExperiment(Config, experiment, userAttributes, Logger)); LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Evaluating audiences for experiment ""feat_with_var_test"": [""3468206642"",""3988293898"",""3988293899"",""3468206646"",""3468206647"",""3468206644"",""3468206643""]"), Times.Once); LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Starting to evaluate audience ""3468206642"" with conditions: [""and"", [""or"", [""or"", {""name"": ""house"", ""type"": ""custom_attribute"", ""value"": ""Gryffindor""}]]]"), Times.Once); LoggerMock.Verify(l => l.Log(LogLevel.INFO, @"Audience ""3468206642"" evaluated to TRUE"), Times.Once); LoggerMock.Verify(l => l.Log(LogLevel.INFO, @"Audiences for experiment ""feat_with_var_test"" collectively evaluated to TRUE"), Times.Once); }
/// <summary> /// Helper function to validate all required conditions before performing activate or track. /// </summary> /// <param name="experiment">Experiment Object representing experiment</param> /// <param name="userId">string ID for user</param> /// <param name="userAttributes">associative array of Attributes for the user</param> private bool ValidatePreconditions(Experiment experiment, string userId, UserAttributes userAttributes = null) { if (!experiment.IsExperimentRunning) { Logger.Log(LogLevel.INFO, string.Format("Experiment {0} is not running.", experiment.Key)); return(false); } if (experiment.IsUserInForcedVariation(userId)) { return(true); } if (!ExperimentUtils.IsUserInExperiment(Config, experiment, userAttributes, Logger)) { Logger.Log(LogLevel.INFO, string.Format("User \"{0}\" does not meet conditions to be in experiment \"{1}\".", userId, experiment.Key)); return(false); } return(true); }
/// <summary> /// Get a Variation of an Experiment for a user to be allocated into. /// </summary> /// <param name = "experiment" > The Experiment the user will be bucketed into.</param> /// <param name = "userId" > The userId of the user. /// <param name = "filteredAttributes" > The user's attributes. This should be filtered to just attributes in the Datafile.</param> /// <returns>The Variation the user is allocated into.</returns> public virtual Variation GetVariation(Experiment experiment, string userId, ProjectConfig config, UserAttributes filteredAttributes) { if (!ExperimentUtils.IsExperimentActive(experiment, Logger)) { return(null); } // check if a forced variation is set var forcedVariation = GetForcedVariation(experiment.Key, userId, config); if (forcedVariation != null) { return(forcedVariation); } var variation = GetWhitelistedVariation(experiment, userId); if (variation != null) { return(variation); } UserProfile userProfile = null; if (UserProfileService != null) { try { Dictionary <string, object> userProfileMap = UserProfileService.Lookup(userId); if (userProfileMap != null && UserProfileUtil.IsValidUserProfileMap(userProfileMap)) { userProfile = UserProfileUtil.ConvertMapToUserProfile(userProfileMap); variation = GetStoredVariation(experiment, userProfile, config); if (variation != null) { return(variation); } } else if (userProfileMap == null) { Logger.Log(LogLevel.INFO, "We were unable to get a user profile map from the UserProfileService."); } else { Logger.Log(LogLevel.ERROR, "The UserProfileService returned an invalid map."); } } catch (Exception exception) { Logger.Log(LogLevel.ERROR, exception.Message); ErrorHandler.HandleError(new Exceptions.OptimizelyRuntimeException(exception.Message)); } } if (ExperimentUtils.IsUserInExperiment(config, experiment, filteredAttributes, Logger)) { // Get Bucketing ID from user attributes. string bucketingId = GetBucketingId(userId, filteredAttributes); variation = Bucketer.Bucket(config, experiment, bucketingId, userId); if (variation != null && variation.Key != null) { if (UserProfileService != null) { var bucketerUserProfile = userProfile ?? new UserProfile(userId, new Dictionary <string, Decision>()); SaveVariation(experiment, variation, bucketerUserProfile); } else { Logger.Log(LogLevel.INFO, "This decision will not be saved since the UserProfileService is null."); } } return(variation); } Logger.Log(LogLevel.INFO, $"User \"{userId}\" does not meet conditions to be in experiment \"{experiment.Key}\"."); return(null); }
/// <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 TestIsUserInExperimentAudienceUsedInExperimentNoAttributesProvided() { Assert.IsFalse(ExperimentUtils.IsUserInExperiment(Config, Config.GetExperimentFromKey("test_experiment"), new UserAttributes())); Assert.IsFalse(ExperimentUtils.IsUserInExperiment(Config, Config.GetExperimentFromKey("test_experiment"), null)); }
public void TestIsUserInExperimentNoAudienceUsedInExperiment() { Assert.IsTrue(ExperimentUtils.IsUserInExperiment(Config, Config.GetExperimentFromKey("paused_experiment"), new UserAttributes())); }