Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Refactor loading of snapshots #20632

Merged
merged 32 commits into from
Apr 16, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
a633392
feat: Refactor loading of snapshots
benjackwhite Feb 29, 2024
ab797d2
Update UI snapshots for `chromium` (2)
github-actions[bot] Feb 29, 2024
4d1c7a7
Update query snapshots
github-actions[bot] Feb 29, 2024
6543e07
Update UI snapshots for `chromium` (2)
github-actions[bot] Feb 29, 2024
3a4c063
Update query snapshots
github-actions[bot] Feb 29, 2024
93353b5
Update UI snapshots for `chromium` (2)
github-actions[bot] Feb 29, 2024
1a7ed41
Update UI snapshots for `chromium` (2)
github-actions[bot] Feb 29, 2024
ce82531
Update UI snapshots for `webkit` (2)
github-actions[bot] Feb 29, 2024
f53b69c
Update UI snapshots for `webkit` (2)
github-actions[bot] Feb 29, 2024
2e95887
Fixes
benjackwhite Feb 29, 2024
1c33af8
Fix tests
benjackwhite Feb 29, 2024
d0cfc9b
Fixes
benjackwhite Feb 29, 2024
aa37022
Update UI snapshots for `chromium` (2)
github-actions[bot] Feb 29, 2024
bdb7775
Fix
benjackwhite Feb 29, 2024
76d4e5b
Fixes
benjackwhite Feb 29, 2024
a73dc1b
Update UI snapshots for `chromium` (2)
github-actions[bot] Feb 29, 2024
b8583de
Updated logs
benjackwhite Feb 29, 2024
6623d03
Fix file size check
benjackwhite Feb 29, 2024
d76809a
Merge branch 'master' into fix/v3-snapshots
benjackwhite Mar 5, 2024
cce7b1e
Update UI snapshots for `webkit` (2)
github-actions[bot] Mar 5, 2024
78f9067
Update UI snapshots for `webkit` (2)
github-actions[bot] Mar 5, 2024
079a6cb
Update UI snapshots for `chromium` (1)
github-actions[bot] Mar 5, 2024
4fd09e5
Update UI snapshots for `webkit` (2)
github-actions[bot] Mar 5, 2024
9c8b11e
Update UI snapshots for `chromium` (1)
github-actions[bot] Mar 5, 2024
6d126ff
Merge branch 'master' into fix/v3-snapshots
daibhin Mar 28, 2024
7d75dc8
minor fixes from review
daibhin Apr 2, 2024
64f0e01
Merge branch 'master' into fix/v3-snapshots
daibhin Apr 4, 2024
60447d4
Merge branch 'master' into fix/v3-snapshots
daibhin Apr 5, 2024
242fdd6
remove etag changes
daibhin Apr 5, 2024
e3307f4
remove unnecessary line break
daibhin Apr 5, 2024
713574c
fix comment
daibhin Apr 5, 2024
8dc8f36
Merge branch 'master' into fix/v3-snapshots
daibhin Apr 16, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@PostHog/team-product-analytics this snapshot seems to be flapping on quite a few PRs this week

Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've shipped some smaller parts of this change in previous PRs and did a thorough review today. It's pretty difficult to review 100% but I'm fairly confident it does what it's intended to. My plan is to test it locally but think it's probably worth shipping at this point and following up with any fixes forward we need.

@benjackwhite is there anything you'd like to do before we ship? There's still a few TODOs knocking about

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just don't have any spare capacity for it so its fully in your hands

Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { eventWithTime } from '@rrweb/types'
import { prepareRecordingSnapshots } from 'scenes/session-recordings/player/sessionRecordingDataLogic'
import { dedupeRecordingSnapshots } from 'scenes/session-recordings/player/sessionRecordingDataLogic'

import { RecordingSnapshot } from '~/types'

Expand Down Expand Up @@ -27,7 +27,7 @@ export const convertSnapshotsResponse = (
snapshotsByWindowId: { [key: string]: eventWithTime[] },
existingSnapshots?: RecordingSnapshot[]
): RecordingSnapshot[] => {
return prepareRecordingSnapshots(convertSnapshotsByWindowId(snapshotsByWindowId), existingSnapshots)
return dedupeRecordingSnapshots([...convertSnapshotsByWindowId(snapshotsByWindowId), ...(existingSnapshots ?? [])])
}

export const sortedRecordingSnapshots = (): { snapshot_data_by_window_id: Record<string, RecordingSnapshot[]> } => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { lemonToast } from '@posthog/lemon-ui'
import { eventWithTime } from '@rrweb/types'
import { BuiltLogic, connect, kea, listeners, path, reducers, selectors } from 'kea'
import { loaders } from 'kea-loaders'
import { beforeUnload } from 'kea-router'
Expand All @@ -11,51 +10,15 @@ import { eventUsageLogic } from 'lib/utils/eventUsageLogic'
import { Scene } from 'scenes/sceneTypes'
import { urls } from 'scenes/urls'

import { Breadcrumb, PersonType, RecordingSnapshot, ReplayTabs, SessionRecordingType } from '~/types'
import { Breadcrumb, ReplayTabs } from '~/types'

import {
dedupeRecordingSnapshots,
parseEncodedSnapshots,
prepareRecordingSnapshots,
sessionRecordingDataLogic,
} from '../player/sessionRecordingDataLogic'
import type { sessionRecordingDataLogicType } from '../player/sessionRecordingDataLogicType'
import type { sessionRecordingFilePlaybackLogicType } from './sessionRecordingFilePlaybackLogicType'

export type ExportedSessionRecordingFileV1 = {
version: '2022-12-02'
data: {
person: PersonType | null
snapshotsByWindowId: Record<string, eventWithTime[]>
}
}

export type ExportedSessionRecordingFileV2 = {
version: '2023-04-28'
data: {
id: SessionRecordingType['id']
person: SessionRecordingType['person']
snapshots: RecordingSnapshot[]
}
}

export const createExportedSessionRecording = (
logic: BuiltLogic<sessionRecordingDataLogicType>,
// DEBUG signal only, to be removed before release
exportUntransformedMobileSnapshotData: boolean
): ExportedSessionRecordingFileV2 => {
const { sessionPlayerMetaData, sessionPlayerSnapshotData } = logic.values

return {
version: '2023-04-28',
data: {
id: sessionPlayerMetaData?.id ?? '',
person: sessionPlayerMetaData?.person,
snapshots: exportUntransformedMobileSnapshotData
? sessionPlayerSnapshotData?.untransformed_snapshots || []
: sessionPlayerSnapshotData?.snapshots || [],
},
}
}
import { ExportedSessionRecordingFileV1, ExportedSessionRecordingFileV2 } from './types'

export const parseExportedSessionRecording = (fileData: string): ExportedSessionRecordingFileV2 => {
const data = JSON.parse(fileData) as ExportedSessionRecordingFileV1 | ExportedSessionRecordingFileV2
Expand Down Expand Up @@ -177,14 +140,15 @@ export const sessionRecordingFilePlaybackLogic = kea<sessionRecordingFilePlaybac
return
}

const snapshots = prepareRecordingSnapshots(
const snapshots = dedupeRecordingSnapshots(
await parseEncodedSnapshots(
values.sessionRecording.snapshots,
values.sessionRecording.id,
!!values.featureFlags[FEATURE_FLAGS.SESSION_REPLAY_MOBILE]
)
)

// TODO: Change to `receiveFilePlaybackData`
dataLogic.actions.loadRecordingSnapshotsSuccess({
snapshots,
})
Expand Down
20 changes: 20 additions & 0 deletions frontend/src/scenes/session-recordings/file-playback/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { eventWithTime } from '@rrweb/types'

import { PersonType, RecordingSnapshot, SessionRecordingType } from '~/types'

export type ExportedSessionRecordingFileV1 = {
version: '2022-12-02'
data: {
person: PersonType | null
snapshotsByWindowId: Record<string, eventWithTime[]>
}
}

export type ExportedSessionRecordingFileV2 = {
version: '2023-04-28'
data: {
id: SessionRecordingType['id']
person: SessionRecordingType['person']
snapshots: RecordingSnapshot[]
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ export const playerInspectorLogic = kea<playerInspectorLogicType>([
[
'sessionPlayerData',
'sessionPlayerMetaDataLoading',
'sessionPlayerSnapshotDataLoading',
'snapshotsLoading',
'sessionEventsData',
'sessionEventsDataLoading',
'windowIds',
Expand Down Expand Up @@ -796,7 +796,7 @@ export const playerInspectorLogic = kea<playerInspectorLogicType>([
(s) => [
s.sessionEventsDataLoading,
s.sessionPlayerMetaDataLoading,
s.sessionPlayerSnapshotDataLoading,
s.snapshotsLoading,
s.sessionEventsData,
s.consoleLogs,
s.allPerformanceEvents,
Expand All @@ -805,27 +805,27 @@ export const playerInspectorLogic = kea<playerInspectorLogicType>([
(
sessionEventsDataLoading,
sessionPlayerMetaDataLoading,
sessionPlayerSnapshotDataLoading,
snapshotsLoading,
events,
logs,
performanceEvents,
doctorEvents
): Record<SessionRecordingPlayerTab, 'loading' | 'ready' | 'empty'> => {
const tabEventsState = sessionEventsDataLoading ? 'loading' : events?.length ? 'ready' : 'empty'
const tabConsoleState =
sessionPlayerMetaDataLoading || sessionPlayerSnapshotDataLoading || !logs
sessionPlayerMetaDataLoading || snapshotsLoading || !logs
? 'loading'
: logs.length
? 'ready'
: 'empty'
const tabNetworkState =
sessionPlayerMetaDataLoading || sessionPlayerSnapshotDataLoading || !performanceEvents
sessionPlayerMetaDataLoading || snapshotsLoading || !performanceEvents
? 'loading'
: performanceEvents.length
? 'ready'
: 'empty'
const tabDoctorState =
sessionPlayerMetaDataLoading || sessionPlayerSnapshotDataLoading || !performanceEvents
sessionPlayerMetaDataLoading || snapshotsLoading || !performanceEvents
? 'loading'
: doctorEvents.length
? 'ready'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
snapshotsAsRealTimeJSONPayload,
} from 'scenes/session-recordings/__mocks__/recording_snapshots'
import {
prepareRecordingSnapshots,
dedupeRecordingSnapshots,
sessionRecordingDataLogic,
} from 'scenes/session-recordings/player/sessionRecordingDataLogic'
import { teamLogic } from 'scenes/teamLogic'
Expand All @@ -30,14 +30,14 @@
start_timestamp: '2023-08-11T12:03:36.097000Z',
end_timestamp: '2023-08-11T12:04:52.268000Z',
blob_key: '1691755416097-1691755492268',
loaded: false,

Check failure on line 33 in frontend/src/scenes/session-recordings/player/sessionRecordingDataLogic.test.ts

View workflow job for this annotation

GitHub Actions / Code quality checks

Type '{ source: "blob"; start_timestamp: string; end_timestamp: string; blob_key: string; loaded: boolean; }' is not assignable to type 'SessionRecordingSnapshotSource'.
}
const REALTIME_SOURCE: SessionRecordingSnapshotSource = {
source: 'realtime',
start_timestamp: '2024-01-28T21:19:49.217000Z',
end_timestamp: undefined,
blob_key: undefined,
loaded: false,

Check failure on line 40 in frontend/src/scenes/session-recordings/player/sessionRecordingDataLogic.test.ts

View workflow job for this annotation

GitHub Actions / Code quality checks

Type '{ source: "realtime"; start_timestamp: string; end_timestamp: undefined; blob_key: undefined; loaded: boolean; }' is not assignable to type 'SessionRecordingSnapshotSource'.
}

describe('sessionRecordingDataLogic', () => {
Expand Down Expand Up @@ -115,7 +115,7 @@
it('loads all data', async () => {
await expectLogic(logic, () => {
logic.actions.loadRecordingMeta()
logic.actions.loadRecordingSnapshots()

Check failure on line 118 in frontend/src/scenes/session-recordings/player/sessionRecordingDataLogic.test.ts

View workflow job for this annotation

GitHub Actions / Code quality checks

Property 'loadRecordingSnapshots' does not exist on type '{ setFilters: (filters: Partial<RecordingEventsFilters>) => void; loadRecordingMeta: () => void; maybeLoadRecordingMeta: () => void; loadSnapshots: () => void; ... 24 more ...; fetchSimilarRecordingsFailure: (error: string, errorObject?: any) => void; }'.
})
.toDispatchActions([
'loadRecordingMetaSuccess',
Expand Down Expand Up @@ -174,7 +174,7 @@
})
logic.mount()
logic.actions.loadRecordingMeta()
logic.actions.loadRecordingSnapshots()

Check failure on line 177 in frontend/src/scenes/session-recordings/player/sessionRecordingDataLogic.test.ts

View workflow job for this annotation

GitHub Actions / Code quality checks

Property 'loadRecordingSnapshots' does not exist on type '{ setFilters: (filters: Partial<RecordingEventsFilters>) => void; loadRecordingMeta: () => void; maybeLoadRecordingMeta: () => void; loadSnapshots: () => void; ... 24 more ...; fetchSimilarRecordingsFailure: (error: string, errorObject?: any) => void; }'.

await expectLogic(logic).toDispatchActions(['loadRecordingMetaSuccess', 'loadRecordingSnapshotsFailure'])
expect(logic.values.sessionPlayerData).toMatchObject({
Expand Down Expand Up @@ -219,7 +219,7 @@
})

await expectLogic(logic, () => {
logic.actions.loadRecordingSnapshots()

Check failure on line 222 in frontend/src/scenes/session-recordings/player/sessionRecordingDataLogic.test.ts

View workflow job for this annotation

GitHub Actions / Code quality checks

Property 'loadRecordingSnapshots' does not exist on type '{ setFilters: (filters: Partial<RecordingEventsFilters>) => void; loadRecordingMeta: () => void; maybeLoadRecordingMeta: () => void; loadSnapshots: () => void; ... 24 more ...; fetchSimilarRecordingsFailure: (error: string, errorObject?: any) => void; }'.
}).toDispatchActions(['loadEvents', 'loadEventsSuccess'])

expect(api.create).toHaveBeenCalledWith(
Expand Down Expand Up @@ -255,7 +255,7 @@
describe('report usage', () => {
it('sends `recording loaded` event only when entire recording has loaded', async () => {
await expectLogic(logic, () => {
logic.actions.loadRecordingSnapshots()

Check failure on line 258 in frontend/src/scenes/session-recordings/player/sessionRecordingDataLogic.test.ts

View workflow job for this annotation

GitHub Actions / Code quality checks

Property 'loadRecordingSnapshots' does not exist on type '{ setFilters: (filters: Partial<RecordingEventsFilters>) => void; loadRecordingMeta: () => void; maybeLoadRecordingMeta: () => void; loadSnapshots: () => void; ... 24 more ...; fetchSimilarRecordingsFailure: (error: string, errorObject?: any) => void; }'.
})
.toDispatchActionsInAnyOrder([
'loadRecordingSnapshots',
Expand All @@ -267,7 +267,7 @@
})
it('sends `recording viewed` and `recording analyzed` event on first contentful paint', async () => {
await expectLogic(logic, () => {
logic.actions.loadRecordingSnapshots()

Check failure on line 270 in frontend/src/scenes/session-recordings/player/sessionRecordingDataLogic.test.ts

View workflow job for this annotation

GitHub Actions / Code quality checks

Property 'loadRecordingSnapshots' does not exist on type '{ setFilters: (filters: Partial<RecordingEventsFilters>) => void; loadRecordingMeta: () => void; maybeLoadRecordingMeta: () => void; loadSnapshots: () => void; ... 24 more ...; fetchSimilarRecordingsFailure: (error: string, errorObject?: any) => void; }'.
})
.toDispatchActions(['loadRecordingSnapshotsSuccess'])
.toDispatchActionsInAnyOrder([
Expand All @@ -278,7 +278,7 @@
})
it('clears the cache after unmounting', async () => {
await expectLogic(logic, () => {
logic.actions.loadRecordingSnapshots()

Check failure on line 281 in frontend/src/scenes/session-recordings/player/sessionRecordingDataLogic.test.ts

View workflow job for this annotation

GitHub Actions / Code quality checks

Property 'loadRecordingSnapshots' does not exist on type '{ setFilters: (filters: Partial<RecordingEventsFilters>) => void; loadRecordingMeta: () => void; maybeLoadRecordingMeta: () => void; loadSnapshots: () => void; ... 24 more ...; fetchSimilarRecordingsFailure: (error: string, errorObject?: any) => void; }'.
})
expect(Object.keys(logic.cache)).toEqual(
expect.arrayContaining(['metaStartTime', 'snapshotsStartTime', 'eventsStartTime'])
Expand All @@ -302,7 +302,7 @@

expect(snapshotsWithDuplicates.length).toEqual(snapshots.length + 2)

expect(prepareRecordingSnapshots(snapshots)).toEqual(prepareRecordingSnapshots(snapshotsWithDuplicates))
expect(dedupeRecordingSnapshots(snapshots)).toEqual(dedupeRecordingSnapshots(snapshotsWithDuplicates))
})

it('should cope with two not duplicate snapshots with the same timestamp and delay', () => {
Expand All @@ -326,13 +326,13 @@
},
]
// we call this multiple times and pass existing data in, so we need to make sure it doesn't change
expect(prepareRecordingSnapshots(verySimilarSnapshots, verySimilarSnapshots)).toEqual(verySimilarSnapshots)
expect(dedupeRecordingSnapshots(verySimilarSnapshots, verySimilarSnapshots)).toEqual(verySimilarSnapshots)

Check failure on line 329 in frontend/src/scenes/session-recordings/player/sessionRecordingDataLogic.test.ts

View workflow job for this annotation

GitHub Actions / Code quality checks

Expected 1 arguments, but got 2.
})

it('should match snapshot', () => {
const snapshots = convertSnapshotsByWindowId(sortedRecordingSnapshotsJson.snapshot_data_by_window_id)

expect(prepareRecordingSnapshots(snapshots)).toMatchSnapshot()
expect(dedupeRecordingSnapshots(snapshots)).toMatchSnapshot()
})
})

Expand All @@ -351,7 +351,7 @@

it('loads each source, and on success reports recording viewed', async () => {
await expectLogic(logic, () => {
logic.actions.loadRecordingSnapshots()

Check failure on line 354 in frontend/src/scenes/session-recordings/player/sessionRecordingDataLogic.test.ts

View workflow job for this annotation

GitHub Actions / Code quality checks

Property 'loadRecordingSnapshots' does not exist on type '{ setFilters: (filters: Partial<RecordingEventsFilters>) => void; loadRecordingMeta: () => void; maybeLoadRecordingMeta: () => void; loadSnapshots: () => void; ... 24 more ...; fetchSimilarRecordingsFailure: (error: string, errorObject?: any) => void; }'.
// loading the snapshots will trigger a loadRecordingSnapshotsSuccess
// that will have the blob source
// that triggers loadRecordingSnapshots
Expand Down
Loading
Loading