コード例 #1
0
ファイル: UsageRuleFactory.cs プロジェクト: shoshiiran/cscore
 public static UsageRule NewFeatureNotUsedXDaysRule(this LocalAnalytics self, string featureId, int days)
 {
     return(new UsageRule(UsageRule.FeatureNotUsedXDays)
     {
         categoryId = featureId, days = days
     }.SetupUsing(self));
 }
コード例 #2
0
ファイル: Ui15_NewsManager.cs プロジェクト: shoshiiran/cscore
        private static async Task AddAFewLocalNewsEvents(InMemoryKeyValueStore onDeviceEventsStore)
        {
            var a = new LocalAnalytics();
            // Set up a few simple usage rules to generate local news events from if they are true:
            var appUsed1DayRule      = a.NewAppUsedXDaysRule(days: 1);
            var featureNeverUsedRule = a.NewFeatureNotUsedXTimesRule("feature1", times: 1);

            var appNotUsedLast5Days = a.NewAppUsedInTheLastXDaysRule(5);
            var userCameBackRule    = a.NewConcatRule(appUsed1DayRule, appNotUsedLast5Days);

            var url     = "https://github.com/cs-util-com/cscore";
            var urlText = "Show details..";

            if (await appUsed1DayRule.isTrue())
            {
                var n = News.NewLocalNewsEvent("Achievement Unlocked", "You used the app 1 day", url, urlText);
                await onDeviceEventsStore.Set(n.key, n);
            }
            if (await featureNeverUsedRule.isTrue())
            {
                var n = News.NewLocalNewsEvent("Did you know you can do feature1?", "Feature 1 is the best", url, urlText);
                await onDeviceEventsStore.Set(n.key, n);
            }
            if (await userCameBackRule.isTrue())
            {
                var n = News.NewLocalNewsEvent("You did not use the app for a while", "How dare you", url, urlText);
                await onDeviceEventsStore.Set(n.key, n);
            }
        }
コード例 #3
0
ファイル: AppFlowTests.cs プロジェクト: r8zbeh/cscore
        public async Task TestLocalAnalytics()
        {
            int eventCount = 10000;

            // Create a LocalAnalytics instance that uses only memory stores for testing:
            LocalAnalytics localAnalytics = new LocalAnalytics(new InMemoryKeyValueStore());

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

            // Pass this local analytics system to the app flow impl. as the target store:
            AppFlowToStore appFlow = new AppFlowToStore(localAnalytics);

            await TestAppFlowWithStore(eventCount, appFlow); // Run the tests

            // Get the store that contains only the events of a specific category:
            var catMethodStore = localAnalytics.GetStoreForCategory(EventConsts.catMethod);
            { // Check that all events so far are of the method category:
                var all = await localAnalytics.GetAll();

                var allForCat = await catMethodStore.GetAll();

                Assert.Equal(all.Count(), allForCat.Count());
            }
            { // Add an event of a different category and check that the numbers again:
                appFlow.TrackEvent(EventConsts.catUi, "Some UI event");
                var all = await localAnalytics.GetAll();

                var allForCat = await catMethodStore.GetAll();

                Assert.Equal(all.Count(), allForCat.Count() + 1);
                var catUiStore = localAnalytics.GetStoreForCategory(EventConsts.catUi);
                Assert.Single(await catUiStore.GetAll());
            }
        }
コード例 #4
0
ファイル: UsageRuleFactory.cs プロジェクト: shoshiiran/cscore
 public static UsageRule NewConcatRule(this LocalAnalytics self, params UsageRule[] andRules)
 {
     return(new UsageRule(UsageRule.ConcatRule)
     {
         andRules = andRules.ToList()
     }.SetupUsing(self));
 }
コード例 #5
0
ファイル: UsageRuleFactory.cs プロジェクト: shoshiiran/cscore
 public static UsageRule NewFeatureNotUsedXTimesRule(this LocalAnalytics self, string featureId, int times)
 {
     return(new UsageRule(UsageRule.FeatureNotUsedXTimes)
     {
         categoryId = featureId, timesUsed = times
     }.SetupUsing(self));
 }
コード例 #6
0
 public ProgressionSystem(LocalAnalytics analytics, FeatureFlagManager <T> featureFlagManager)
 {
     // Make sure the FeatureFlag system was set up too:
     AssertV2.IsNotNull(FeatureFlagManager <T> .instance, "FeatureFlagManager.instance");
     this.analytics          = analytics;
     this.featureFlagManager = featureFlagManager;
 }
コード例 #7
0
ファイル: UsageRuleFactory.cs プロジェクト: shoshiiran/cscore
 public static UsageRule NewNotificationMinXDaysOldRule(this LocalAnalytics self, string notificationId, int days)
 {
     return(new UsageRule(UsageRule.NotificationMinXDaysOld)
     {
         categoryId = notificationId, days = days
     }.SetupUsing(self));
 }
コード例 #8
0
ファイル: UsageRuleFactory.cs プロジェクト: shoshiiran/cscore
 public static UsageRule NewAppNotUsedXDaysRule(this LocalAnalytics self, int days)
 {
     return(new UsageRule(UsageRule.AppNotUsedXDays)
     {
         days = days
     }.SetupUsing(self));
 }
コード例 #9
0
        private static LocalAnalytics CreateLocalAnalyticsSystem()
        {
            LocalAnalytics analytics = new LocalAnalytics(new InMemoryKeyValueStore());

            analytics.createStoreFor = (_) => new InMemoryKeyValueStore().GetTypeAdapter <AppFlowEvent>();
            // Setup the AppFlow logic to use the LocalAnalytics system:
            AppFlow.AddAppFlowTracker(new AppFlowToStore(analytics));
            return(analytics);
        }
コード例 #10
0
        async Task AssertFeatureUsageDetected(LocalAnalytics analytics, string featureId, int expectedCount)
        {
            var featureEventStore = analytics.categoryStores[featureId];
            var allFeatureEvents  = await featureEventStore.GetAll();

            Assert.Equal(expectedCount, allFeatureEvents.Count());
            var allStartEvents = allFeatureEvents.Filter(x => x.action == EventConsts.START);

            Assert.Equal(expectedCount, allStartEvents.Count());
        }
コード例 #11
0
ファイル: ProgressionSystem.cs プロジェクト: r8zbeh/cscore
        public static void Setup(DefaultFeatureFlagStore featureFlagStore)
        {
            IoC.inject.SetSingleton(new FeatureFlagManager(featureFlagStore));
            LocalAnalytics analytics = new LocalAnalytics();

            AppFlow.AddAppFlowTracker(new AppFlowToStore(analytics));
            var xpSystem = new DefaultProgressionSystem(analytics);

            IoC.inject.SetSingleton <ProgressionSystem>(xpSystem);
        }
コード例 #12
0
ファイル: FeatureFlagTests.cs プロジェクト: shoshiiran/cscore
        // Cleanup from previous tests (needed because persistance to disc is used):
        private static async Task CleanupFilesFromTest(LocalAnalytics analytics, ProgressionSystem <FeatureFlag> xpSystem)
        {
            // First trigger 1 event for each relevant catory to load the category stores:
            foreach (var category in xpSystem.xpFactors.Keys)
            {
                AppFlow.TrackEvent(category, "Dummy Event");
            }
            await analytics.RemoveAll();                // Then clear all stores

            Assert.Empty(await analytics.GetAllKeys()); // Now the main store should be emtpy
        }
コード例 #13
0
        async Task TestAllRules1(LocalAnalytics analytics, string featureId)
        {
            var daysUsed  = 20;
            var timesUsed = 200;

            UsageRule featureUsedXDays = analytics.NewFeatureUsedXDaysRule(featureId, daysUsed);

            Assert.False(await featureUsedXDays.isTrue());
            Assert.False(await featureUsedXDays.IsFeatureUsedXDays(analytics)); // Used by .isTrue

            UsageRule featureNotUsedXDays = analytics.NewFeatureNotUsedXDaysRule(featureId, daysUsed);

            Assert.True(await featureNotUsedXDays.isTrue());

            UsageRule appNotUsedXDays = analytics.NewAppNotUsedXDaysRule(daysUsed);

            Assert.True(await appNotUsedXDays.isTrue());

            UsageRule featureNotUsedXTimes = analytics.NewFeatureNotUsedXTimesRule(featureId, timesUsed);

            Assert.True(await featureNotUsedXTimes.isTrue());

            UsageRule appUsedInTheLastXDays = analytics.NewAppUsedInTheLastXDaysRule(daysUsed);

            Assert.True(await appUsedInTheLastXDays.isTrue());

            UsageRule appNotUsedInTheLastXDays = analytics.NewAppNotUsedInTheLastXDaysRule(daysUsed);

            Assert.False(await appNotUsedInTheLastXDays.isTrue());

            UsageRule featureUsedInTheLastXDays = analytics.NewFeatureUsedInTheLastXDaysRule(featureId, daysUsed);

            Assert.True(await featureUsedInTheLastXDays.isTrue());

            { // Compose a more complex usage rule out of multiple rules:
                UsageRule appUsedXDays = analytics.NewAppUsedXDaysRule(daysUsed);
                Assert.False(await appUsedXDays.isTrue());

                UsageRule featureUsedXTimes = analytics.NewFeatureUsedXTimesRule(featureId, timesUsed);
                Assert.False(await featureUsedXTimes.isTrue());

                UsageRule featureNotUsedInTheLastXDays = analytics.NewFeatureNotUsedInTheLastXDaysRule(featureId, daysUsed);
                Assert.False(await featureNotUsedInTheLastXDays.isTrue());

                UsageRule featureNotUsedAnymoreRule = analytics.NewConcatRule(
                    appUsedXDays, featureUsedXTimes, featureNotUsedInTheLastXDays
                    );
                Assert.False(await featureNotUsedAnymoreRule.isTrue());

                UsageRule clone = featureNotUsedAnymoreRule.DeepCopyViaJson();
                clone.SetupUsing(analytics);
                Assert.False(await clone.isTrue());
            }
        }
コード例 #14
0
ファイル: FeatureFlagTests.cs プロジェクト: shoshiiran/cscore
        private static async Task <ProgressionSystem <FeatureFlag> > NewInMemoryTestXpSystem(string apiKey, string sheetId, string sheetName)
        {
            var cachedFlags          = new InMemoryKeyValueStore();
            var googleSheetsStore    = new GoogleSheetsKeyValueStore(cachedFlags, apiKey, sheetId, sheetName);
            var cachedFlagsLocalData = new InMemoryKeyValueStore();
            var analytics            = new LocalAnalytics(new InMemoryKeyValueStore());

            analytics.createStoreFor = (_ => new InMemoryKeyValueStore().GetTypeAdapter <AppFlowEvent>());
            var featureFlagStore = new FeatureFlagStore(cachedFlagsLocalData, googleSheetsStore);

            return(await DefaultProgressionSystem.Setup(featureFlagStore, analytics));
        }
コード例 #15
0
        public static UsageRule SetupUsing(this UsageRule self, LocalAnalytics analytics)
        {
            self.isTrue = async() => {
                switch (self.ruleType)
                {
                case UsageRule.AppUsedXDays: return(await self.IsAppUsedXDays(analytics));

                case UsageRule.AppNotUsedXDays: return(!await self.IsAppUsedXDays(analytics));

                case UsageRule.AppUsedInTheLastXDays: return(self.IsAppUsedInTheLastXDays());

                case UsageRule.AppNotUsedInTheLastXDays: return(!self.IsAppUsedInTheLastXDays());

                case UsageRule.FeatureUsedInTheLastXDays: return(await self.IsFeatureUsedInTheLastXDays(analytics));

                case UsageRule.FeatureNotUsedInTheLastXDays: return(!await self.IsFeatureUsedInTheLastXDays(analytics));

                case UsageRule.FeatureUsedXDays: return(await self.IsFeatureUsedXDays(analytics));

                case UsageRule.FeatureNotUsedXDays: return(!await self.IsFeatureUsedXDays(analytics));

                case UsageRule.FeatureUsedXTimes: return(await self.IsFeatureUsedXTimes(analytics));

                case UsageRule.FeatureNotUsedXTimes: return(!await self.IsFeatureUsedXTimes(analytics));

                case UsageRule.NotificationMinXDaysOld: return(await self.IsNotificationMinXDaysOld(analytics));

                case UsageRule.ConcatRule:
                    foreach (var rule in self.andRules)
                    {
                        if (rule.isTrue == null)
                        {
                            rule.SetupUsing(analytics);
                        }
                        if (!await rule.isTrue())
                        {
                            return(false);
                        }
                    }
                    return(true);

                default:
                    Log.e("Unknown ruleType: " + self.ruleType);
                    return(false);
                }
            };
            return(self);
        }
コード例 #16
0
ファイル: FeatureFlagTests.cs プロジェクト: shoshiiran/cscore
        public async Task TestDefaultProgressionSystem()
        {
            // 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 ffm = new FeatureFlagManager <FeatureFlag>(new FeatureFlagStore(new InMemoryKeyValueStore(), googleSheetsStore));

            IoC.inject.SetSingleton <FeatureFlagManager <FeatureFlag> >(ffm);

            LocalAnalytics analytics = new LocalAnalytics();

            AppFlow.AddAppFlowTracker(new AppFlowToStore(analytics));
            var xpSystem = new ProgressionSystem <FeatureFlag>(analytics, ffm);

            IoC.inject.SetSingleton <IProgressionSystem <FeatureFlag> >(xpSystem);

            await CleanupFilesFromTest(analytics, xpSystem);

            // Simulate User progression by causing analytics events:
            var eventCount = 1000;

            for (int i = 0; i < eventCount; i++)
            {
                AppFlow.TrackEvent(EventConsts.catMutation, "User did mutation nr " + i);
            }

            var flagId4 = "MyFlag4";
            var flag4   = await FeatureFlagManager <FeatureFlag> .instance.GetFeatureFlag(flagId4);

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

            // Now that the user has 1000 XP the condition of the TestXpSystem is met:
            Assert.True(await FeatureFlag.IsEnabled(flagId4));

            // The number of mutation events:
            Assert.Equal(eventCount, xpSystem.cachedCategoryCounts[EventConsts.catMutation]);
            // Since there are only mutation events the XP is equal to the factor*event count:
            Assert.Equal(await xpSystem.GetLatestXp(), eventCount * xpSystem.xpFactors[EventConsts.catMutation]);

            await CleanupFilesFromTest(analytics, xpSystem);
        }
コード例 #17
0
        public static async Task <ProgressionSystem <FeatureFlag> > Setup(KeyValueStoreTypeAdapter <FeatureFlag> featureFlagStore, LocalAnalytics analytics)
        {
            var ffm = new FeatureFlagManager <FeatureFlag>(featureFlagStore);

            IoC.inject.SetSingleton(ffm);
            AppFlow.AddAppFlowTracker(new AppFlowToStore(analytics).WithBasicTrackingActive());
            var xpSystem = new ProgressionSystem <FeatureFlag>(analytics, ffm);

            IoC.inject.SetSingleton <IProgressionSystem <FeatureFlag> >(xpSystem);
            await xpSystem.UpdateCurrentCategoryCounts();

            return(xpSystem);
        }
コード例 #18
0
        public static async Task <bool> IsFeatureUsedXDays(this UsageRule self, LocalAnalytics analytics)
        {
            var allEvents = await analytics.GetAllEventsForCategory(self.categoryId);

            return(allEvents.GroupByDay().Count() >= self.days);
        }
コード例 #19
0
        public static async Task <bool> IsNotificationMinXDaysOld(this UsageRule self, LocalAnalytics analytics)
        {
            var allEvents = await analytics.GetAllEventsForCategory(EventConsts.catUsage);

            var showEvents = allEvents.Filter(x => x.action == EventConsts.SHOW + "_" + self.categoryId);

            if (showEvents.IsNullOrEmpty())
            {
                return(false);
            }
            DateTime firstShownEvent = showEvents.First().GetDateTimeUtc();
            TimeSpan firstShownVsNow = DateTimeV2.UtcNow - firstShownEvent;

            return(firstShownVsNow.Days >= self.days);
        }
コード例 #20
0
ファイル: FeatureFlagTests.cs プロジェクト: shoshiiran/cscore
        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));
        }
コード例 #21
0
        public static async Task <bool> IsAppUsedXDays(this UsageRule self, LocalAnalytics analytics)
        {
            var allEvents = await analytics.GetAll();

            return(allEvents.GroupByDay().Count() >= self.days);
        }
コード例 #22
0
ファイル: ProgressionSystem.cs プロジェクト: r8zbeh/cscore
 public DefaultProgressionSystem(LocalAnalytics analytics)
 {
     // Make sure the FeatureFlag system was set up too:
     AssertV2.NotNull(FeatureFlagManager.instance, "FeatureFlagManager.instance");
     this.analytics = analytics;
 }
コード例 #23
0
 private static async Task <IEnumerable <AppFlowEvent> > GetAllEventsForCategory(this LocalAnalytics self, string categoryId)
 {
     if (!self.categoryStores.ContainsKey(categoryId))
     {
         return(Enumerable.Empty <AppFlowEvent>());
     }
     return(await self.categoryStores[categoryId].GetAll());
 }
コード例 #24
0
        public static async Task <bool> IsFeatureUsedInTheLastXDays(this UsageRule self, LocalAnalytics analytics)
        {
            var allEvents = await analytics.GetAllEventsForCategory(self.categoryId);

            if (allEvents.IsNullOrEmpty())
            {
                return(false);
            }
            DateTime lastEvent      = allEvents.Last().GetDateTimeUtc();
            TimeSpan lastEventVsNow = DateTimeV2.UtcNow - lastEvent;

            return(lastEventVsNow.Days <= self.days);
        }
コード例 #25
0
        public static async Task <IEnumerable <UsageRule> > GetRulesInitialized(this KeyValueStoreTypeAdapter <UsageRule> self, LocalAnalytics analytics)
        {
            var rules = await self.GetAll();

            foreach (var rule in rules)
            {
                if (!rule.concatRuleIds.IsNullOrEmpty())
                {
                    rule.andRules = new List <UsageRule>();
                    foreach (var id in rule.concatRuleIds)
                    {
                        rule.andRules.Add(await self.Get(id, null));
                    }
                }
                if (rule.isTrue == null)
                {
                    rule.SetupUsing(analytics);
                }
            }
            return(rules);
        }
コード例 #26
0
        public static async Task <bool> IsFeatureUsedXTimes(this UsageRule self, LocalAnalytics analytics)
        {
            var startEvents = await analytics.GetStartEvents(self.categoryId);

            return(startEvents.Count() >= self.timesUsed.Value);
        }
コード例 #27
0
        public static async Task <IEnumerable <AppFlowEvent> > GetStartEvents(this LocalAnalytics self, string categoryId)
        {
            var allEvents = await self.GetAllEventsForCategory(categoryId);

            return(allEvents.Filter(x => x.action == EventConsts.START));
        }