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); } }
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()); } }
// 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 }
[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")); }
/// <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; }; }); }
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)); }
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); }
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 }
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); } }
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); }
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()); }
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); }
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()); }
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 LogLowMemory() { AppFlow.TrackEvent(EventConsts.catError, "System reports: Low memory warning!"); }