From 77b1e4908704c18927466ff449302e8a1dcd69d2 Mon Sep 17 00:00:00 2001 From: Joe Bergeron Date: Mon, 2 Dec 2024 18:19:04 -0500 Subject: [PATCH] fix(analytics): Send redux_store_size event regularly (#6271) ### Description Currently, we have a mechanism in place to log the user's redux store size as an analytics event, however due to a bug in the logic, it only ever gets logged if a user keeps a session open for >24 hours. This PR updates the logic to ensure that the redux store size will be logged as soon as it's first available in a new session, and then subsequently every minute, whenever the full state is serialized. ### Test plan Unit and manual tested. ### Related issues - Fixes #[issue number here] ### Backwards compatibility ### Network scalability If a new NetworkId and/or Network are added in the future, the changes in this PR will: - [x] Continue to work without code changes, OR trigger a compilation error (guaranteeing we find it when a new network is added) --- src/redux/store.test.ts | 30 +++++++++++++++++++++++++++++- src/redux/store.ts | 8 +++++--- src/utils/time.ts | 2 +- 3 files changed, 35 insertions(+), 5 deletions(-) diff --git a/src/redux/store.test.ts b/src/redux/store.test.ts index b14070bf184..8d66af5b885 100644 --- a/src/redux/store.test.ts +++ b/src/redux/store.test.ts @@ -5,10 +5,12 @@ import * as createMigrateModule from 'src/redux/createMigrate' import { migrations } from 'src/redux/migrations' import { RootState } from 'src/redux/reducers' import { rootSaga } from 'src/redux/sagas' -import { _persistConfig, setupStore } from 'src/redux/store' +import { _persistConfig, setupStore, timeBetweenStoreSizeEvents } from 'src/redux/store' import * as accountCheckerModule from 'src/utils/accountChecker' import Logger from 'src/utils/Logger' import { getLatestSchema, vNeg1Schema } from 'test/schemas' +import AppAnalytics from 'src/analytics/AppAnalytics' +import { PerformanceEvents } from 'src/analytics/Events' // Mock sagas because we don't want them to run in this test jest.mock('src/redux/sagas', () => ({ @@ -44,11 +46,16 @@ function getNonApiReducers>(state: RootStat beforeEach(() => { jest.clearAllMocks() + jest.useFakeTimers() // For some reason createMigrate.mockRestore doesn't work, so instead we manually reset it to the original implementation createMigrate.mockImplementation(originalCreateMigrate) resetStateOnInvalidStoredAccount.mockImplementation((state) => Promise.resolve(state)) }) +afterAll(() => { + jest.useRealTimers() +}) + describe('persistConfig', () => { it('points to the latest migration', () => { const migrationKeys = Object.keys(migrations) @@ -72,6 +79,27 @@ describe('persistConfig', () => { expect(createMigrate).toHaveBeenCalledTimes(1) expect(resetStateOnInvalidStoredAccount).toHaveBeenCalledTimes(1) }) + + it('sends redux store size when available and after cooldown', () => { + const dateNow = Date.now() + jest.setSystemTime(dateNow) + const serialize = _persistConfig?.serialize as unknown as (data: any) => string + serialize({ _persist: true, foo: 'bar' }) + expect(AppAnalytics.track).toHaveBeenCalledTimes(1) + expect(AppAnalytics.track).toHaveBeenCalledWith(PerformanceEvents.redux_store_size, { + size: 29, + }) + // Should not track again until delay has passed + serialize({ _persist: true, foo: 'bar' }) + expect(AppAnalytics.track).toHaveBeenCalledTimes(1) + // Should track again after delay has passed + jest.setSystemTime(new Date(dateNow + 2 * timeBetweenStoreSizeEvents).getTime()) + serialize({ _persist: true, foo: 'bar', bar: 'baz' }) + expect(AppAnalytics.track).toHaveBeenCalledTimes(2) + expect(AppAnalytics.track).toHaveBeenCalledWith(PerformanceEvents.redux_store_size, { + size: 41, + }) + }) }) describe('store state', () => { diff --git a/src/redux/store.ts b/src/redux/store.ts index 253548891c6..690cb4ad762 100644 --- a/src/redux/store.ts +++ b/src/redux/store.ts @@ -15,10 +15,12 @@ import { rootSaga } from 'src/redux/sagas' import { transactionFeedV2Api } from 'src/transactions/api' import { resetStateOnInvalidStoredAccount } from 'src/utils/accountChecker' import Logger from 'src/utils/Logger' -import { ONE_DAY_IN_MILLIS } from 'src/utils/time' +import { ONE_MINUTE_IN_MILLIS } from 'src/utils/time' -const timeBetweenStoreSizeEvents = ONE_DAY_IN_MILLIS -let lastEventTime = Date.now() +export const timeBetweenStoreSizeEvents = ONE_MINUTE_IN_MILLIS +// Set this to the epoch so that a redix_store_size event will always be emitted the first time +// the entire state is serialized in a session +let lastEventTime = 0 const persistConfig: PersistConfig = { key: 'root', diff --git a/src/utils/time.ts b/src/utils/time.ts index eeb8abd60fb..c861e2bf907 100644 --- a/src/utils/time.ts +++ b/src/utils/time.ts @@ -38,7 +38,7 @@ export const formatDistanceToNow = ( } const ONE_SECOND_IN_MILLIS = 1000 -const ONE_MINUTE_IN_MILLIS = 60 * ONE_SECOND_IN_MILLIS +export const ONE_MINUTE_IN_MILLIS = 60 * ONE_SECOND_IN_MILLIS export const ONE_HOUR_IN_MILLIS = 60 * ONE_MINUTE_IN_MILLIS export const ONE_DAY_IN_MILLIS = 24 * ONE_HOUR_IN_MILLIS