Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
22 changes: 11 additions & 11 deletions frontend/src/scenes/session-recordings/player/PurePlayer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
)
}

export function PurePlayer({ noMeta = false, noBorder = false }: PurePlayerProps): JSX.Element {

Check warning on line 57 in frontend/src/scenes/session-recordings/player/PurePlayer.tsx

View workflow job for this annotation

GitHub Actions / Frontend formatting

lint:complexity

`PurePlayer` has cyclomatic complexity 27 (warn >10)
const playerRef = useRef<HTMLDivElement | null>(null)
const [playerContainer, setPlayerContainer] = useState<HTMLDivElement | null>(null)
const playerCallbackRef = useCallback((el: HTMLDivElement | null) => {
Expand Down Expand Up @@ -109,8 +109,9 @@
isRecordingDeleted,
recordingDeletedAt,
recordingDeletedBy,
snapshotCheckState,
} = useValues(sessionRecordingDataCoordinatorLogic(logicProps))
const { loadSnapshots } = useActions(sessionRecordingDataCoordinatorLogic(logicProps))
const { checkForNewSnapshots } = useActions(sessionRecordingDataCoordinatorLogic(logicProps))

const { isPlaylistCollapsed, showMetadataFooter } = useValues(playerSettingsLogic)
const { setPlaylistCollapsed } = useActions(playerSettingsLogic)
Expand Down Expand Up @@ -368,18 +369,11 @@
The snapshot of the screen taken when this recording started never
reached PostHog, so there is nothing to play back. This usually happens
when the browser is closed or goes offline before the recording finishes
uploading.{' '}
uploading. Reloading won't bring this data back.{' '}
<Link to="https://posthog.com/docs/session-replay/troubleshooting">
Learn more
</Link>
</p>
<LemonButton
data-attr="player-error-retry-load"
type="secondary"
onClick={loadSnapshots}
>
Reload
</LemonButton>
</>
) : (
<>
Expand All @@ -392,10 +386,16 @@
<LemonButton
data-attr="player-error-retry-load-still-working"
type="secondary"
onClick={loadSnapshots}
onClick={checkForNewSnapshots}
loading={snapshotCheckState === 'checking'}
>
Reload
Check for new data
</LemonButton>
{snapshotCheckState === 'checked' ? (
<p className="text-secondary text-sm mt-2 mb-0">
It's not ready to play yet. Try again in a minute.
</p>
) : null}
</>
)}
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -422,6 +422,23 @@ describe('sessionRecordingDataCoordinatorLogic', () => {
isOldAndInvalid: logic.values.isOldAndInvalid,
}).toEqual(expected)
})
it('settles a viewer-requested check for new data while a background poll is in flight', async () => {
mountWithSnapshots(incrementalOnlySnapshotsAsJSONLines(dayjs().subtract(1, 'minute').valueOf()))
await loadFully()
expect(logic.values.snapshotCheckState).toBe('idle')

jest.useFakeTimers()
try {
logic.actions.loadSnapshotSources(60000)
logic.actions.checkForNewSnapshots()
expect(logic.values.snapshotCheckState).toBe('checking')

await jest.advanceTimersByTimeAsync(1000)
expect(logic.values.snapshotCheckState).toBe('checked')
} finally {
jest.useRealTimers()
}
})
})

// TODO need deduplication tests for blob_v2 sources before we deprecate blob_v1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,11 @@
return start != null && now().diff(start, 'minute') <= INGESTION_GRACE_PERIOD_MINUTES
}

export type SnapshotCheckState = 'idle' | 'checking' | 'checked'

const settleSnapshotCheck = (state: SnapshotCheckState): SnapshotCheckState =>
state === 'checking' ? 'checked' : state

// Generated by kea-typegen. Update if you're an agent, ignore if you're human.
export interface sessionRecordingDataCoordinatorLogicValues {
sessionComments: CommentType[] // commentsLogic
Expand Down Expand Up @@ -283,6 +288,7 @@
reportedLoaded: boolean
segments: RecordingSegment[]
sessionPlayerData: SessionPlayerData
snapshotCheckState: SnapshotCheckState
snapshots: RecordingSnapshot[]
snapshotsByWindowId: Record<number, eventWithTime[]>
snapshotsInvalid: boolean
Expand Down Expand Up @@ -450,9 +456,32 @@
metadata: SessionRecordingType | null
playerData: SessionPlayerData
} // sessionRecordingEventUsageLogic
loadSnapshotSourcesFailure: (
error: string,
errorObject?: any
) => {
error: string
errorObject?: any
} // snapLogic
loadSnapshotSourcesSuccess: (
snapshotSources: import('@posthog/replay-shared').SessionRecordingSnapshotSource[],
payload?:
| {
breakpointLength: number | undefined
}
| undefined
) => {
payload?: {
breakpointLength: number | undefined
}
snapshotSources: SessionRecordingSnapshotSource[]
} // snapLogic
storeUpdated: () => {
value: true
} // snapLogic
checkForNewSnapshots: () => {
value: true
}
loadRecordingData: () => {
value: true
}
Expand Down Expand Up @@ -614,7 +643,7 @@
'loadRecordingNotebookCommentsSuccess',
],
snapLogic,
['storeUpdated'],
['loadSnapshotSourcesFailure', 'loadSnapshotSourcesSuccess', 'storeUpdated'],
],
values: [
metaLogic,
Expand Down Expand Up @@ -667,6 +696,9 @@
// this to an error state — without it the affected sources stay unpromoted forever and the
// player buffers with no error surfaced.
snapshotProcessingFailed: true,
// Background polling stops after POLLING_INACTIVITY_TIMEOUT_MS without a source change, so a
// viewer needs a way to look again and see the outcome of that look.
checkForNewSnapshots: true,
}),
reducers(() => ({
reportedLoaded: [
Expand All @@ -681,12 +713,28 @@
setProcessedSnapshots: (_, { snapshots }) => snapshots,
},
],
// Only a viewer-requested check reaches 'checked', so a background poll cannot report a check
// that the viewer did not ask for.
snapshotCheckState: [
'idle' as SnapshotCheckState,
{
checkForNewSnapshots: () => 'checking',
loadSnapshotSourcesSuccess: settleSnapshotCheck,
loadSnapshotSourcesFailure: settleSnapshotCheck,
},
],
})),
listeners(({ values, actions, props, cache }) => ({
loadRecordingData: () => {
actions.loadRecordingMeta()
},

checkForNewSnapshots: () => {
// Call the loader directly: loadSnapshots skips while a background poll waits at its
// breakpoint, so the click would wait out the poll interval. A new loader call cancels that wait.
actions.loadSnapshotSources()
},

loadRecordingMetaSuccess: () => {
if (props.sessionRecordingId && !values.recordingTooLargeToPlay) {
actions.loadSnapshotSources()
Expand Down Expand Up @@ -737,7 +785,7 @@
actions.reportUsageIfFullyLoaded()
},

processSnapshotsAsync: async (_, breakpoint) => {

Check warning on line 788 in frontend/src/scenes/session-recordings/player/sessionRecordingDataCoordinatorLogic.ts

View workflow job for this annotation

GitHub Actions / Frontend formatting

lint:complexity

`processSnapshotsAsync` has cyclomatic complexity 14 (warn >10)
cache.processingCache = cache.processingCache || { snapshots: {} }

const sources = values.snapshotSources
Expand Down
Loading