Пример #1
0
 /// <summary>
 /// Get the variation the user is bucketed into for the FeatureFlag
 /// </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 any variation or the FeatureDecision entity if the user is
 /// successfully bucketed.</returns>
 public virtual Result <FeatureDecision> GetVariationForFeature(FeatureFlag featureFlag, OptimizelyUserContext user, ProjectConfig config)
 {
     return(GetVariationForFeature(featureFlag, user, config, user.GetAttributes(), new OptimizelyDecideOption[] { }));
 }
Пример #2
0
 /// <summary>
 /// Get the variation the user is bucketed into for the FeatureFlag
 /// </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 any variation or the FeatureDecision entity if the user is
 /// successfully bucketed.</returns>
 public virtual Result <FeatureDecision> GetVariationForFeature(FeatureFlag featureFlag, string userId, ProjectConfig config, UserAttributes filteredAttributes)
 {
     return(GetVariationForFeature(featureFlag, userId, config, filteredAttributes, new OptimizelyDecideOption[] { }));
 }
Пример #3
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));
        }
 public static string ToJsonString(this FeatureFlag flag) =>
 DataModelSerialization.SerializeFlag(flag);
Пример #5
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,
                                                                              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));
        }
Пример #6
0
        public static bool IsFeatureEnabled(FeatureFlag featureFlag)
        {
            bool?flagValue = GetFeatureFlagValue(featureFlag);

            return(flagValue.HasValue && flagValue.Value);
        }
Пример #7
0
 // For testing only
 internal FlagBuilder PreconfiguredFlag(FeatureFlag preconfiguredFlag)
 {
     _preconfiguredFlag = preconfiguredFlag;
     return(this);
 }
Пример #8
0
 public void EnableFeature(FeatureFlag feature)
 {
     m_FeatureFlag |= feature;
 }
Пример #9
0
 public void DisableFeature(FeatureFlag feature)
 {
     m_FeatureFlag = m_FeatureFlag & (~feature);
 }
 internal static bool UpsertFlag(IDataStore store, FeatureFlag item) =>
 store.Upsert(DataModel.Features, item.Key, DescriptorOf(item));
Пример #11
0
 public bool HasFeature(FeatureFlag feature)
 {
     return((m_FeatureFlag & feature) != 0);
 }
 internal static ItemDescriptor DescriptorOf(FeatureFlag item) => new ItemDescriptor(item.Version, item);
 public DataSetBuilder Add(string key, FeatureFlag flag)
 {
     _items.Add(new KeyValuePair <string, ItemDescriptor>(key, flag.ToItemDescriptor()));
     return(this);
 }
 public static string ToJsonString(this FeatureFlag flag, string key) =>
 LdValue.BuildObject().Copy(LdValue.Parse(flag.ToJsonString())).Set("key", key).Build().ToJsonString();
Пример #15
0
        public async Task <Payload <bool> > DeleteFeatureFlag(FeatureFlag featureFlag)
        {
            Uri url = new Uri($"api/FeatureFlags/DeleteFeatureFlag", UriKind.Relative);

            return(await PostMessageItem <bool>(url, featureFlag));
        }
Пример #16
0
        public async Task <bool> AddFeatureFlag(FeatureFlag featureFlag)
        {
            Uri url = new Uri($"api/FeatureFlags/SaveFeatureFlag", UriKind.Relative);

            return(await PostMessageItem <bool>(url, featureFlag));
        }
Пример #17
0
        protected override void Seed(FeatureFlags.Data.DataContext context)
        {
            //  This method will be called after migrating to the latest version.

            //  You can use the DbSet<T>.AddOrUpdate() helper extension method
            //  to avoid creating duplicate seed data. E.g.
            //
            //    context.People.AddOrUpdate(
            //      p => p.FullName,
            //      new Person { FullName = "Andrew Peters" },
            //      new Person { FullName = "Brice Lambson" },
            //      new Person { FullName = "Rowan Miller" }
            //    );
            //

            var flag = new FeatureFlag {
                Key = new Guid("F803A4D0-F7BF-4513-BAAC-EDDB4477BB94"), FlagKeyValue = "Task.Screen", EffectiveDate = DateTime.Now
            };
            var providerFlag = new FeatureFlag {
                Key = new Guid("3C25EE27-AE91-42BD-9CD4-6F6B579D3B2B"), FlagKeyValue = "Task.Provider", EffectiveDate = DateTime.Now
            };

            context.FeatureFlag.AddOrUpdate(f => f.Key,
                                            flag,
                                            providerFlag
                                            );

            var featureRole = new FeatureRole {
                Key = new Guid("1CC07636-7109-47D8-B647-C12CA1BB3BD3"), RoleName = "TaskTester", EffectiveDate = DateTime.Now
            };

            context.FeatureRole.AddOrUpdate(r => r.Key,
                                            featureRole
                                            );

            var featureRoleUser = new FeatureRoleUser {
                Key = new Guid("5A9C2523-A7E5-466F-A70D-9593D72CDC63"), UserName = "", Role = featureRole, EffectiveDate = DateTime.Now
            };

            context.FeatureRoleUser.AddOrUpdate(u => u.Key,
                                                featureRoleUser
                                                );

            var featureStateOff = new FeatureState {
                Key = new Guid("51EF79AF-3F6A-4DCC-AC42-A81DE3ECFE8B"), StateCode = "Off", StateDisplay = "Off", EffectiveDate = DateTime.Now
            };
            var featureStateOn = new FeatureState {
                Key = new Guid("0AD57AAC-1EB8-467B-B6C3-C4E3860929B5"), StateCode = "On", StateDisplay = "On", EffectiveDate = DateTime.Now
            };
            var featureStatePreview = new FeatureState {
                Key = new Guid("A09390BA-4D99-4E8A-8A89-E8487663E42F"), StateCode = "Preview", StateDisplay = "Preview", EffectiveDate = DateTime.Now
            };

            context.FeatureState.AddOrUpdate(s => s.Key,
                                             featureStateOff,
                                             featureStateOn,
                                             featureStatePreview
                                             );

            //var featureStateRole = new FeatureStateRole { Key = new Guid("9C9EC0A5-C8CF-490F-B658-91F8DCC5CF5F"), Flag = flag, RoleUser = featureRoleUser, State = featureStateOff, EffectiveDate = DateTime.Now };
            //context.FeatureStateRole.AddOrUpdate(fsr => fsr.Key,
            //    featureStateRole
            //    );

            var featureProviderStateRole = new FeatureStateRole {
                Key = new Guid("F9240688-A3C8-40FF-B6BB-BBCD2A9F2B37"), Flag = providerFlag, RoleUser = featureRoleUser, State = featureStatePreview, EffectiveDate = DateTime.Now
            };

            context.FeatureStateRole.AddOrUpdate(fsr => fsr.Key,
                                                 featureProviderStateRole
                                                 );

            context.SaveChanges();
        }
 private void AssertFeatureInStore(FeatureFlag f)
 {
     Assert.Equal(f.Version, _featureStore.Get(VersionedDataKind.Features, f.Key).Version);
 }
Пример #19
0
        public static bool IsFeatureEnabled(int vehicle, FeatureFlag flag)
        {
            var state = (FeatureFlag)API.DecorGetInt(vehicle, FeatureFlagsDecor);

            return((state & flag) == flag);
        }
Пример #20
0
 private void AssertFeatureInStore(FeatureFlag f)
 {
     Assert.Equal(f.Version, _dataStore.Get(DataModel.Features, f.Key).Value.Version);
 }
Пример #21
0
        public async Task TestProgressiveDisclosure()
        {
            // Get your key from https://console.developers.google.com/apis/credentials
            var apiKey = "AIzaSyCtcFQMgRIUHhSuXggm4BtXT4eZvUrBWN0";
            // https://docs.google.com/spreadsheets/d/1KBamVmgEUX-fyogMJ48TT6h2kAMKyWU1uBL5skCGRBM contains the sheetId:
            var sheetId           = "1KBamVmgEUX-fyogMJ48TT6h2kAMKyWU1uBL5skCGRBM";
            var sheetName         = "MySheet1"; // Has to match the sheet name
            var googleSheetsStore = new GoogleSheetsKeyValueStore(new InMemoryKeyValueStore(), apiKey, sheetId, sheetName);
            var testStore         = new FeatureFlagStore(new InMemoryKeyValueStore(), googleSheetsStore);

            IoC.inject.SetSingleton <FeatureFlagManager <FeatureFlag> >(new FeatureFlagManager <FeatureFlag>(testStore));

            // Make sure user would normally be included in the rollout:
            var flagId4 = "MyFlag4";
            var flag4   = await FeatureFlagManager <FeatureFlag> .instance.GetFeatureFlag(flagId4);

            Assert.Equal(flagId4, flag4.id);
            Assert.Equal(100, flag4.rolloutPercentage);

            // There is no user progression system setup so the requiredXp value of the feature flag is ignored:
            Assert.True(await FeatureFlag.IsEnabled(flagId4));
            Assert.True(await flag4.IsFeatureUnlocked());

            // Setup progression system and check again:
            var xpSystem = new TestXpSystem();

            IoC.inject.SetSingleton <IProgressionSystem <FeatureFlag> >(xpSystem);
            // Now that there is a progression system
            Assert.False(await flag4.IsFeatureUnlocked());
            Assert.False(await FeatureFlag.IsEnabled(flagId4));

            var eventCount = 1000;

            var store = new MutationObserverKeyValueStore().WithFallbackStore(new InMemoryKeyValueStore());

            // Lets assume the users xp correlates with the number of triggered local analytics events:
            store.onSet = delegate {
                xpSystem.currentXp++;
                return(Task.FromResult(true));
            };

            // Set the store to be the target of the local analytics so that whenever any
            LocalAnalytics analytics = new LocalAnalytics(store);

            analytics.createStoreFor = (_) => new InMemoryKeyValueStore().GetTypeAdapter <AppFlowEvent>();

            // Setup the AppFlow logic to use LocalAnalytics:
            AppFlow.AddAppFlowTracker(new AppFlowToStore(analytics));

            // Simulate User progression by causing analytics events:
            for (int i = 0; i < eventCount; i++)
            {
                AppFlow.TrackEvent(EventConsts.catMutation, "User did mutation nr " + i);
            }

            // Get the analtics store for category "Mutations":
            var mutationStore = await analytics.GetStoreForCategory(EventConsts.catMutation).GetAll();

            Assert.Equal(eventCount, mutationStore.Count()); // All events so far were mutations
            Assert.True(eventCount <= xpSystem.currentXp);   // The user received xp for each mutation

            Assert.Equal(1000, flag4.requiredXp);            // The user needs >= 1000 xp for the feature

            // Now that the user has more than 1000 xp the condition of the TestXpSystem is met:
            Assert.True(await flag4.IsFeatureUnlocked());
            Assert.True(await FeatureFlag.IsEnabled(flagId4));
        }
        private void AssertFlagHasAllProperties(FeatureFlag flag)
        {
            Assert.Equal("flag-key", flag.Key);
            Assert.Equal(99, flag.Version);
            Assert.True(flag.On);
            Assert.Equal("123", flag.Salt);

            Assert.Collection(flag.Prerequisites,
                              p =>
            {
                Assert.Equal("prereqkey", p.Key);
                Assert.Equal(3, p.Variation);
            });

            Assert.Collection(flag.Targets,
                              t =>
            {
                Assert.Equal(1, t.Variation);
                Assert.Equal(ImmutableList.Create("key1", "key2"), t.Values);
            });

            Assert.Collection(flag.Rules,
                              r =>
            {
                Assert.Equal("id0", r.Id);
                Assert.True(r.TrackEvents);
                Assert.Equal(2, r.Variation);
                Assert.Null(r.Rollout);
                Assert.Collection(r.Clauses,
                                  c =>
                {
                    Assert.Equal(UserAttribute.Name, c.Attribute);
                    Assert.Equal(Operator.In, c.Op);
                    Assert.Equal(ImmutableList.Create(LdValue.Of("Lucy"), LdValue.Of("Mina")), c.Values);
                    Assert.True(c.Negate);
                });
            },
                              r =>
            {
                Assert.Equal("id1", r.Id);
                Assert.False(r.TrackEvents);
                Assert.Null(r.Variation);
                Assert.NotNull(r.Rollout);
                Assert.Collection(r.Rollout.Value.Variations,
                                  v =>
                {
                    Assert.Equal(2, v.Variation);
                    Assert.Equal(100000, v.Weight);
                });
                Assert.Equal(UserAttribute.Email, r.Rollout.Value.BucketBy);
                Assert.Collection(r.Clauses);
            });

            Assert.NotNull(flag.Fallthrough);
            Assert.Equal(1, flag.Fallthrough.Variation);
            Assert.Null(flag.Fallthrough.Rollout);
            Assert.Equal(2, flag.OffVariation);
            Assert.Equal(ImmutableList.Create(LdValue.Of("a"), LdValue.Of("b"), LdValue.Of("c")), flag.Variations);
            Assert.True(flag.ClientSide);
            Assert.True(flag.TrackEvents);
            Assert.True(flag.TrackEventsFallthrough);
            Assert.Equal(UnixMillisecondTime.OfMillis(1000), flag.DebugEventsUntilDate);
        }