diff --git a/frontend/src/scenes/session-recordings/apm/playerInspector/ItemPerformanceEvent.tsx b/frontend/src/scenes/session-recordings/apm/playerInspector/ItemPerformanceEvent.tsx index e195319f037f9..9b1d2f1a1e64f 100644 --- a/frontend/src/scenes/session-recordings/apm/playerInspector/ItemPerformanceEvent.tsx +++ b/frontend/src/scenes/session-recordings/apm/playerInspector/ItemPerformanceEvent.tsx @@ -65,7 +65,7 @@ export interface ItemPerformanceEvent { item: PerformanceEvent expanded: boolean setExpanded: (expanded: boolean) => void - finalTimestamp?: Dayjs + finalTimestamp: Dayjs | null } function renderTimeBenchmark(milliseconds: number): JSX.Element { diff --git a/frontend/src/scenes/session-recordings/player/inspector/playerInspectorLogic.ts b/frontend/src/scenes/session-recordings/player/inspector/playerInspectorLogic.ts index e83851eb58894..207491cbc0fe0 100644 --- a/frontend/src/scenes/session-recordings/player/inspector/playerInspectorLogic.ts +++ b/frontend/src/scenes/session-recordings/player/inspector/playerInspectorLogic.ts @@ -143,7 +143,7 @@ function snapshotDescription(snapshot: eventWithTime): string { function timeRelativeToStart( thingWithTime: eventWithTime | PerformanceEvent | RecordingConsoleLogV2 | RecordingEventType, - start: Dayjs | undefined + start: Dayjs | null ): { timeInRecording: number timestamp: dayjs.Dayjs diff --git a/frontend/src/scenes/session-recordings/player/sessionRecordingDataLogic.test.ts b/frontend/src/scenes/session-recordings/player/sessionRecordingDataLogic.test.ts index 8fbadb4fe773d..13279a313cf97 100644 --- a/frontend/src/scenes/session-recordings/player/sessionRecordingDataLogic.test.ts +++ b/frontend/src/scenes/session-recordings/player/sessionRecordingDataLogic.test.ts @@ -94,8 +94,8 @@ describe('sessionRecordingDataLogic', () => { expect(logic.values).toMatchObject({ bufferedToTime: null, durationMs: 0, - start: undefined, - end: undefined, + start: null, + end: null, segments: [], sessionEventsData: null, filters: {}, @@ -146,8 +146,8 @@ describe('sessionRecordingDataLogic', () => { .toMatchValues({ sessionPlayerData: { bufferedToTime: null, - start: undefined, - end: undefined, + start: null, + end: null, durationMs: 0, segments: [], sessionRecordingId: '2', diff --git a/frontend/src/scenes/session-recordings/player/sessionRecordingDataLogic.ts b/frontend/src/scenes/session-recordings/player/sessionRecordingDataLogic.ts index ee412b93ea076..1db07b7544720 100644 --- a/frontend/src/scenes/session-recordings/player/sessionRecordingDataLogic.ts +++ b/frontend/src/scenes/session-recordings/player/sessionRecordingDataLogic.ts @@ -245,7 +245,10 @@ async function processEncodedResponse( } const getSourceKey = (source: SessionRecordingSnapshotSource): string => { - return `${source.source}-${source.blob_key}` + // realtime sources vary so blob_key is not always present and is either null or undefined... + // we only care about key when not realtime + // and we'll always have a key when not realtime + return `${source.source}-${source.blob_key || source.source}` } export const sessionRecordingDataLogic = kea([ @@ -751,29 +754,52 @@ export const sessionRecordingDataLogic = kea([ }, ], + firstSnapshot: [ + (s) => [s.snapshots], + (snapshots): RecordingSnapshot | null => { + return snapshots[0] || null + }, + ], + + lastSnapshot: [ + (s) => [s.snapshots], + (snapshots): RecordingSnapshot | null => { + return snapshots[snapshots.length - 1] || null + }, + ], + start: [ - (s) => [s.sessionPlayerMetaData], - (meta): Dayjs | undefined => { - return meta?.start_time ? dayjs(meta.start_time) : undefined + (s) => [s.firstSnapshot, s.sessionPlayerMetaData], + (firstSnapshot, meta): Dayjs | null => { + const eventStart = meta?.start_time ? dayjs(meta.start_time) : null + const snapshotStart = firstSnapshot ? dayjs(firstSnapshot.timestamp) : null + + // whichever is earliest + if (eventStart && snapshotStart) { + return eventStart.isBefore(snapshotStart) ? eventStart : snapshotStart + } + return eventStart || snapshotStart }, ], end: [ - (s) => [s.sessionPlayerMetaData, s.snapshots], - (meta, snapshots): Dayjs | undefined => { - // NOTE: We might end up with more snapshots than we knew about when we started the recording so we - // either use the metadata end point or the last snapshot, whichever is later. - const end = meta?.end_time ? dayjs(meta.end_time) : undefined - const lastEvent = snapshots?.slice(-1)[0] - - return lastEvent?.timestamp && lastEvent.timestamp > +(end ?? 0) ? dayjs(lastEvent.timestamp) : end + (s) => [s.lastSnapshot, s.sessionPlayerMetaData], + (lastSnapshot, meta): Dayjs | null => { + const eventEnd = meta?.end_time ? dayjs(meta.end_time) : null + const snapshotEnd = lastSnapshot ? dayjs(lastSnapshot.timestamp) : null + + // whichever is latest + if (eventEnd && snapshotEnd) { + return eventEnd.isAfter(snapshotEnd) ? eventEnd : snapshotEnd + } + return eventEnd || snapshotEnd }, ], durationMs: [ (s) => [s.start, s.end], (start, end): number => { - return end?.diff(start) ?? 0 + return !!start && !!end ? end.diff(start) : 0 }, ], diff --git a/frontend/src/scenes/session-recordings/player/utils/segmenter.ts b/frontend/src/scenes/session-recordings/player/utils/segmenter.ts index a8ffe60efb57e..456dc1e7ebd43 100644 --- a/frontend/src/scenes/session-recordings/player/utils/segmenter.ts +++ b/frontend/src/scenes/session-recordings/player/utils/segmenter.ts @@ -46,8 +46,8 @@ export const mapSnapshotsToWindowId = (snapshots: RecordingSnapshot[]): Record { let segments: RecordingSegment[] = [] diff --git a/frontend/src/types.ts b/frontend/src/types.ts index b0b40afa5ab2e..6409502958538 100644 --- a/frontend/src/types.ts +++ b/frontend/src/types.ts @@ -913,8 +913,8 @@ export interface SessionPlayerData { bufferedToTime: number | null snapshotsByWindowId: Record durationMs: number - start?: Dayjs - end?: Dayjs + start: Dayjs | null + end: Dayjs | null fullyLoaded: boolean sessionRecordingId: SessionRecordingId }