Skip to content

Commit

Permalink
fix(analytics): Send redux_store_size event regularly (#6271)
Browse files Browse the repository at this point in the history
### 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

<!-- Brief explanation of why these changes are/are not backwards
compatible. -->

### 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)
  • Loading branch information
jophish authored Dec 2, 2024
1 parent 538a3d9 commit 77b1e49
Show file tree
Hide file tree
Showing 3 changed files with 35 additions and 5 deletions.
30 changes: 29 additions & 1 deletion src/redux/store.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => ({
Expand Down Expand Up @@ -44,11 +46,16 @@ function getNonApiReducers<R = Omit<RootState, ApiReducersKeys>>(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)
Expand All @@ -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', () => {
Expand Down
8 changes: 5 additions & 3 deletions src/redux/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<ReducersRootState> = {
key: 'root',
Expand Down
2 changes: 1 addition & 1 deletion src/utils/time.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down

0 comments on commit 77b1e49

Please sign in to comment.