Beispiel #1
0
        public virtual void LogMethodDone(Stopwatch timing, int maxAllowedTimeInMs, string sourceMemberName, string sourceFilePath, int sourceLineNumber)
        {
            var    timingV2   = timing as StopwatchV2;
            string methodName = sourceMemberName;

            if (timingV2 != null)
            {
                timingV2.StopV2();
                methodName = timingV2.methodName;
            }
            else
            {
                timing.Stop();
            }
            AppFlow.TrackEvent(EventConsts.catMethod, methodName + " DONE", timing);
            var text = "    <-- " + methodName + " finished after " + timing.ElapsedMilliseconds + " ms";

            if (timingV2 != null)
            {
                text += ", " + timingV2.GetAllocatedMemBetweenStartAndStop();
            }
            text = $"{text} \n at {sourceFilePath}: line {sourceLineNumber}";
            Log.d(text, new StackFrame(1, true).AddTo(null));
            if (maxAllowedTimeInMs > 0)
            {
                timing.AssertUnderXms(maxAllowedTimeInMs);
            }
        }
Beispiel #2
0
        public async Task ExampleUsage1()
        {
            // Create an analytics system and fill it with feature usage events:
            var analytics = CreateLocalAnalyticsSystem();

            var t = new MockDateTimeV2();

            IoC.inject.SetSingleton <DateTimeV2>(t);

            string featureId = "feature1";

            {
                var startDate = DateTimeV2.ParseUtc("01.01.2011");
                var endDate   = DateTimeV2.ParseUtc("19.01.2011");

                SimulateUsage(featureId, 100, t, startDate, endDate);
                await AssertFeatureUsageDetected(analytics, featureId, 100);
            }
            t.mockUtcNow = DateTimeV2.ParseUtc("01.02.2011");
            await TestAllRules1(analytics, featureId);

            { // Simulate more usage in the next 5 days:
                var startDate = DateTimeV2.ParseUtc("20.01.2011");
                var endDate   = DateTimeV2.ParseUtc("25.01.2011");

                SimulateUsage(featureId, 100, t, startDate, endDate);
                await AssertFeatureUsageDetected(analytics, featureId, 200);
            }
            // Simulate a month without any usage:
            t.mockUtcNow = DateTimeV2.ParseUtc("01.03.2011");
            await TestAllRules2(analytics, featureId);

            { // Simulate that a usage notification is shown and test the related rule:
                var notificationId = "notification1";
                var daysAgo        = 20;

                t.mockUtcNow = DateTimeV2.ParseUtc("01.03.2011");
                // Simulate that notification1 is shown to the user (e.g. by the usageRule system):
                AppFlow.TrackEvent(EventConsts.catUsage, EventConsts.SHOW + "_" + notificationId);

                t.mockUtcNow = DateTimeV2.ParseUtc("02.03.2011"); // Simulate a day passing by
                UsageRule notificationMinXDaysOld = analytics.NewNotificationMinXDaysOldRule(notificationId, daysAgo);
                Assert.False(await notificationMinXDaysOld.isTrue());
                Assert.False(await notificationMinXDaysOld.IsNotificationMinXDaysOld(analytics));

                t.mockUtcNow = DateTimeV2.ParseUtc("25.03.2011"); // Simulate more time passing by
                Assert.True(await notificationMinXDaysOld.IsNotificationMinXDaysOld(analytics));
                Assert.True(await notificationMinXDaysOld.isTrue());

                // Simulate a second show of the notification:
                AppFlow.TrackEvent(EventConsts.catUsage, EventConsts.SHOW + "_" + notificationId);
                Assert.True(await notificationMinXDaysOld.IsNotificationMinXDaysOld(analytics));
                Assert.True(await notificationMinXDaysOld.isTrue());
            }
        }
Beispiel #3
0
        // 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
        }
Beispiel #4
0
        [Fact] // Event tracking should by default not interrupt the normal application flow:
        public void TestErrorInTrackEvent()
        {
            var tracker = new AppFlowThatThrowsErrors();

            AppFlow.AddAppFlowTracker(tracker);
            // Even though TrackEvent does not work correctly the application flow is not interrupted:
            AppFlow.TrackEvent("category 1", "action 1");
            IoC.inject.Get <IAppFlow>(this).TrackEvent("category 1", "action 2");
            // Directly using the tracker would not protect against the inner exception:
            Assert.Throws <InvalidOperationException>(() => tracker.TrackEvent("category 1", "action 3"));
        }
Beispiel #5
0
 /// <summary> This middleware will automatically log all dispatched actions to the store to the AppFlow logic to track them there </summary>
 public static Middleware <T> NewAppFlowTrackerMiddleware <T>(object extraArgument = null)
 {
     return((IDataStore <T> store) => {
         return (Dispatcher innerDispatcher) => {
             Dispatcher outerDispatcher = (action) => {
                 AppFlow.TrackEvent(EventConsts.catMutation, "" + action, action);
                 return innerDispatcher(action);
             };
             return outerDispatcher;
         };
     });
 }
Beispiel #6
0
        public virtual StopwatchV2 LogMethodEntered(string methodName, object[] args)
        {
#if DEBUG
            args = new StackFrame(2, true).AddTo(args);
#endif
            Log.d(" --> " + methodName, args);
            if (!methodName.IsNullOrEmpty())
            {
                AppFlow.TrackEvent(EventConsts.catMethod, methodName, args);
            }
            return(AssertV2.TrackTiming(methodName));
        }
Beispiel #7
0
        public void RegisterInjector <T>(object injector, Func <object, bool, T> createServiceAction)
        {
            var injectorName = GetEventKey <T>();

            injectorNames = injectorNames.Add(injectorName);
            if (HasInjectorRegistered <T>())
            {
                Log.w("There are already injectors registered for " + injectorName);
            }
            usedEventBus.Subscribe(injector, injectorName, createServiceAction);
            AppFlow.TrackEvent(EventConsts.catInjection, "Register_" + injectorName);
        }
Beispiel #8
0
        public async Task ExampleUsage2()
        {
            // 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 xpSys     = await DefaultProgressionSystem.SetupWithGSheets(apiKey, sheetId, sheetName);

            // The DefaultProgressionSystem will give 1 xp for each mutation:
            AppFlow.TrackEvent(EventConsts.catMutation, "Some mutation"); // Would also be triggered by DataStore
            Assert.NotEqual(0, await xpSys.GetLatestXp());
            Assert.True(await FeatureFlag.IsEnabled("MyFlag5"));          // MyFlag5 needs minimum 1 xp
        }
Beispiel #9
0
        void SimulateUsage(string featureId, int evCount, MockDateTimeV2 t, DateTime start, DateTime end)
        {
            double s            = start.ToUnixTimestampUtc();
            double durationInMs = (end - start).TotalMilliseconds;

            Assert.NotEqual(0, durationInMs);

            for (int i = 0; i < evCount; i++)
            {
                double percent = i / (double)evCount;
                Assert.InRange(percent, 0, 1);
                t.mockUtcNow = DateTimeV2.NewDateTimeFromUnixTimestamp((long)(s + percent * durationInMs));
                AppFlow.TrackEvent(featureId, EventConsts.START);
            }
        }
Beispiel #10
0
 public bool TriggerSwitchView()
 {
     if (!TrySwitchView())
     {
         var isForwardOrBackward = switchDirection != SwitchDirection.loadNextScreenViaPrefab;
         if (isForwardOrBackward && destroyViewStackWhenLastScreenReached)
         {
             gameObject.GetViewStack().gameObject.Destroy();
             return(true);
         }
         Log.w("Cant switch screen in direction " + switchDirection);
     }
     AppFlow.TrackEvent(EventConsts.catView, "switchViewWasRejected");
     return(false);
 }
Beispiel #11
0
        public async Task TestDefaultProgressionSystemSetup()
        {
            // 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 cachedFlags          = FileBasedKeyValueStore.New("FeatureFlags");
            var cachedFlagsLocalData = FileBasedKeyValueStore.New("FeatureFlags_LocalData");
            var googleSheetsStore    = new GoogleSheetsKeyValueStore(cachedFlags, apiKey, sheetId, sheetName);

            DefaultProgressionSystem.Setup(new TestFeatureFlagStore(cachedFlagsLocalData, googleSheetsStore));

            AppFlow.TrackEvent(EventConsts.catMethod, "Some event");
            Assert.False(await FeatureFlag.IsEnabled("MyFlag4"));
            Log.d("Now take a look at the folders in " + cachedFlags.folderForAllFiles.Parent.GetFullFileSystemPath());
        }
Beispiel #12
0
        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);
        }
Beispiel #13
0
        public async Task ExtensiveDefaultProgressionSystemTests()
        {
            // 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
            ProgressionSystem <FeatureFlag> xpSys = await NewInMemoryTestXpSystem(apiKey, sheetId, sheetName);

            Assert.Empty(await xpSys.analytics.GetStoreForCategory(EventConsts.catMutation).GetAllKeys());
            Assert.Equal(0, await xpSys.GetLatestXp());

            Assert.False(await FeatureFlag.IsEnabled("MyFlag5")); // Needs 1 xp

            // The DefaultProgressionSystem will give 1 xp for each mutation:
            AppFlow.TrackEvent(EventConsts.catMutation, "Some mutation"); // Would also be triggered by DataStore
            Assert.Single(await xpSys.analytics.GetStoreForCategory(EventConsts.catMutation).GetAllKeys());
            Assert.NotEqual(0, await xpSys.GetLatestXp());

            Assert.False(await FeatureFlag.IsEnabled("MyFlag4")); // Needs 1000 xp
            Assert.True(await FeatureFlag.IsEnabled("MyFlag5"));  // Needs 1 xp

            await GetLockedAndUnlockedFeatures(xpSys, xpSys.GetLastCachedXp());
        }
Beispiel #14
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));
        }
Beispiel #15
0
 private void LogLowMemory()
 {
     AppFlow.TrackEvent(EventConsts.catError, "System reports: Low memory warning!");
 }