diff --git a/frontend/__snapshots__/scenes-app-insights--funnel-top-to-bottom-edit--light.png b/frontend/__snapshots__/scenes-app-insights--funnel-top-to-bottom-edit--light.png index ebbdd65ee5643..3a86dc325987a 100644 Binary files a/frontend/__snapshots__/scenes-app-insights--funnel-top-to-bottom-edit--light.png and b/frontend/__snapshots__/scenes-app-insights--funnel-top-to-bottom-edit--light.png differ diff --git a/frontend/__snapshots__/scenes-other-onboarding--onboarding-billing--dark.png b/frontend/__snapshots__/scenes-other-onboarding--onboarding-billing--dark.png index a4e1979e55cf6..4748657f64e2d 100644 Binary files a/frontend/__snapshots__/scenes-other-onboarding--onboarding-billing--dark.png and b/frontend/__snapshots__/scenes-other-onboarding--onboarding-billing--dark.png differ diff --git a/frontend/__snapshots__/scenes-other-toolbar--events-debugger-empty--dark.png b/frontend/__snapshots__/scenes-other-toolbar--events-debugger-empty--dark.png index 819445ff0060d..1d2ef8694de75 100644 Binary files a/frontend/__snapshots__/scenes-other-toolbar--events-debugger-empty--dark.png and b/frontend/__snapshots__/scenes-other-toolbar--events-debugger-empty--dark.png differ diff --git a/frontend/__snapshots__/scenes-other-toolbar--events-debugger-empty--light.png b/frontend/__snapshots__/scenes-other-toolbar--events-debugger-empty--light.png index 04dd24b8ec91d..5847e08ebc0a0 100644 Binary files a/frontend/__snapshots__/scenes-other-toolbar--events-debugger-empty--light.png and b/frontend/__snapshots__/scenes-other-toolbar--events-debugger-empty--light.png differ diff --git a/frontend/__snapshots__/scenes-other-toolbar--events-debugger-empty-dark--dark.png b/frontend/__snapshots__/scenes-other-toolbar--events-debugger-empty-dark--dark.png index 6b8fe86fd9941..f0f28d4fc91e8 100644 Binary files a/frontend/__snapshots__/scenes-other-toolbar--events-debugger-empty-dark--dark.png and b/frontend/__snapshots__/scenes-other-toolbar--events-debugger-empty-dark--dark.png differ diff --git a/frontend/__snapshots__/scenes-other-toolbar--events-debugger-empty-dark--light.png b/frontend/__snapshots__/scenes-other-toolbar--events-debugger-empty-dark--light.png index 5fff2e13f4e21..148ac6cae53ff 100644 Binary files a/frontend/__snapshots__/scenes-other-toolbar--events-debugger-empty-dark--light.png and b/frontend/__snapshots__/scenes-other-toolbar--events-debugger-empty-dark--light.png differ diff --git a/frontend/src/lib/utils/eventUsageLogic.ts b/frontend/src/lib/utils/eventUsageLogic.ts index bc0014c36ad88..39487ede8f5e0 100644 --- a/frontend/src/lib/utils/eventUsageLogic.ts +++ b/frontend/src/lib/utils/eventUsageLogic.ts @@ -441,8 +441,6 @@ export const eventUsageLogic = kea([ reportRecordingsListPropertiesFetched: (loadTime: number) => ({ loadTime }), reportRecordingsListFilterAdded: (filterType: SessionRecordingFilterType) => ({ filterType }), reportRecordingPlayerSeekbarEventHovered: true, - reportRecordingPlayerSpeedChanged: (newSpeed: number) => ({ newSpeed }), - reportRecordingPlayerSkipInactivityToggled: (skipInactivity: boolean) => ({ skipInactivity }), reportRecordingInspectorItemExpanded: (tab: InspectorListItemType, index: number) => ({ tab, index }), reportRecordingInspectorMiniFilterViewed: (minifilterKey: MiniFilterKey, enabled: boolean) => ({ minifilterKey, @@ -948,12 +946,6 @@ export const eventUsageLogic = kea([ reportRecordingPlayerSeekbarEventHovered: () => { posthog.capture('recording player seekbar event hovered') }, - reportRecordingPlayerSpeedChanged: ({ newSpeed }) => { - posthog.capture('recording player speed changed', { new_speed: newSpeed }) - }, - reportRecordingPlayerSkipInactivityToggled: ({ skipInactivity }) => { - posthog.capture('recording player skip inactivity toggled', { skip_inactivity: skipInactivity }) - }, reportRecordingInspectorItemExpanded: ({ tab, index }) => { posthog.capture('recording inspector item expanded', { tab: 'replay-4000', type: tab, index }) }, diff --git a/frontend/src/scenes/session-recordings/player/PlayerMeta.tsx b/frontend/src/scenes/session-recordings/player/PlayerMeta.tsx index 6bac98a9fc381..5c811872d4134 100644 --- a/frontend/src/scenes/session-recordings/player/PlayerMeta.tsx +++ b/frontend/src/scenes/session-recordings/player/PlayerMeta.tsx @@ -1,6 +1,6 @@ import './PlayerMeta.scss' -import { LemonBanner, LemonSelect, LemonSelectOption, Link } from '@posthog/lemon-ui' +import { LemonSelect, LemonSelectOption, Link } from '@posthog/lemon-ui' import clsx from 'clsx' import { useActions, useValues } from 'kea' import { CopyToClipboardInline } from 'lib/components/CopyToClipboard' @@ -59,26 +59,6 @@ function URLOrScreen({ lastUrl }: { lastUrl: string | undefined }): JSX.Element ) } -function PlayerWarningsRow(): JSX.Element | null { - const { messageTooLargeWarnings } = useValues(sessionRecordingPlayerLogic) - - return messageTooLargeWarnings.length ? ( -
- - This session recording had recording data that was too large and could not be captured. This will mean - playback is not 100% accurate.{' '} - -
- ) : null -} - export function PlayerMeta({ iconsOnly }: { iconsOnly: boolean }): JSX.Element { const { logicProps, isFullScreen } = useValues(sessionRecordingPlayerLogic) @@ -206,7 +186,6 @@ export function PlayerMeta({ iconsOnly }: { iconsOnly: boolean }): JSX.Element { {resolutionView} - ) diff --git a/frontend/src/scenes/session-recordings/player/PlayerMetaLinks.tsx b/frontend/src/scenes/session-recordings/player/PlayerMetaLinks.tsx index 69f3541aa3ed6..42b8b7d317768 100644 --- a/frontend/src/scenes/session-recordings/player/PlayerMetaLinks.tsx +++ b/frontend/src/scenes/session-recordings/player/PlayerMetaLinks.tsx @@ -57,6 +57,7 @@ function PinToPlaylistButton({ /> ) : ( : } {...buttonProps} @@ -135,7 +136,7 @@ export function PlayerMetaLinks({ iconsOnly }: { iconsOnly: boolean }): JSX.Elem {buttonContent('Comment')} - } onClick={onShare} {...commonProps}> + } onClick={onShare} {...commonProps} tooltip="Share this recording"> {buttonContent('Share')} @@ -149,6 +150,7 @@ export function PlayerMetaLinks({ iconsOnly }: { iconsOnly: boolean }): JSX.Elem attrs: { id: sessionRecordingId }, }) }} + tooltip="Comment in a notebook" /> ) : null} diff --git a/frontend/src/scenes/session-recordings/player/SessionRecordingPlayer.tsx b/frontend/src/scenes/session-recordings/player/SessionRecordingPlayer.tsx index 6b28feca120ae..d70a3267a2532 100644 --- a/frontend/src/scenes/session-recordings/player/SessionRecordingPlayer.tsx +++ b/frontend/src/scenes/session-recordings/player/SessionRecordingPlayer.tsx @@ -4,7 +4,6 @@ import { LemonButton } from '@posthog/lemon-ui' import clsx from 'clsx' import { BindLogic, useActions, useValues } from 'kea' import { BuilderHog2 } from 'lib/components/hedgehogs' -import { dayjs } from 'lib/dayjs' import { FloatingContainerContext } from 'lib/hooks/useFloatingContainerContext' import { HotkeysInterface, useKeyboardHotkeys } from 'lib/hooks/useKeyboardHotkeys' import { usePageVisibility } from 'lib/hooks/usePageVisibility' @@ -87,11 +86,9 @@ export function SessionRecordingPlayer(props: SessionRecordingPlayerProps): JSX. setSpeed, closeExplorer, } = useActions(sessionRecordingPlayerLogic(logicProps)) - const { isNotFound, snapshotsInvalid, start } = useValues(sessionRecordingDataLogic(logicProps)) + const { isNotFound, isRecentAndInvalid } = useValues(sessionRecordingDataLogic(logicProps)) const { loadSnapshots } = useActions(sessionRecordingDataLogic(logicProps)) - const { isFullScreen, explorerMode, isBuffering, messageTooLargeWarnings } = useValues( - sessionRecordingPlayerLogic(logicProps) - ) + const { isFullScreen, explorerMode, isBuffering } = useValues(sessionRecordingPlayerLogic(logicProps)) const { setPlayNextAnimationInterrupted } = useActions(sessionRecordingPlayerLogic(logicProps)) const speedHotkeys = useMemo(() => createPlaybackSpeedKey(setSpeed), [setSpeed]) const { isVerticallyStacked, sidebarOpen, playbackMode } = useValues(playerSettingsLogic) @@ -158,9 +155,6 @@ export function SessionRecordingPlayer(props: SessionRecordingPlayerProps): JSX. } ) - const lessThanFiveMinutesOld = dayjs().diff(start, 'minute') <= 5 - const cannotPlayback = snapshotsInvalid && lessThanFiveMinutesOld && !messageTooLargeWarnings - const { draggable, elementProps } = useNotebookDrag({ href: urls.replaySingle(sessionRecordingId) }) if (isNotFound) { @@ -198,7 +192,7 @@ export function SessionRecordingPlayer(props: SessionRecordingPlayerProps): JSX. className="SessionRecordingPlayer__main flex flex-col h-full w-full" ref={playerMainRef} > - {cannotPlayback ? ( + {isRecentAndInvalid ? (

We're still working on it

diff --git a/frontend/src/scenes/session-recordings/player/controller/PlayerController.tsx b/frontend/src/scenes/session-recordings/player/controller/PlayerController.tsx index 0498fb9efe05f..f539c787f7209 100644 --- a/frontend/src/scenes/session-recordings/player/controller/PlayerController.tsx +++ b/frontend/src/scenes/session-recordings/player/controller/PlayerController.tsx @@ -82,7 +82,7 @@ function ShowMouseTail(): JSX.Element { return ( setTimestampFormat(TimestampFormat.UTC), + active: timestampFormat === TimestampFormat.UTC, + }, + { + label: 'Device', + onClick: () => setTimestampFormat(TimestampFormat.Device), + active: timestampFormat === TimestampFormat.Device, + }, + { + label: 'Relative', + onClick: () => setTimestampFormat(TimestampFormat.Relative), + active: timestampFormat === TimestampFormat.Relative, + }, + ]} + icon={} + label={TimestampFormatToLabel[timestampFormat]} + /> + ) +} + function InspectDOM(): JSX.Element { const { sessionPlayerMetaData } = useValues(sessionRecordingPlayerLogic) const { openExplorer } = useActions(sessionRecordingPlayerLogic) @@ -125,39 +155,15 @@ function InspectDOM(): JSX.Element { } function PlayerBottomSettings(): JSX.Element { - const { timestampFormat } = useValues(playerSettingsLogic) - const { setTimestampFormat } = useActions(playerSettingsLogic) - return (
- setTimestampFormat(TimestampFormat.UTC), - active: timestampFormat === TimestampFormat.UTC, - }, - { - label: 'Device', - onClick: () => setTimestampFormat(TimestampFormat.Device), - active: timestampFormat === TimestampFormat.Device, - }, - { - label: 'Relative', - onClick: () => setTimestampFormat(TimestampFormat.Relative), - active: timestampFormat === TimestampFormat.Relative, - }, - ]} - icon={} - label={TimestampFormatToLabel[timestampFormat]} - /> - +
+
) } diff --git a/frontend/src/scenes/session-recordings/player/playerSettingsLogic.ts b/frontend/src/scenes/session-recordings/player/playerSettingsLogic.ts index 98958a186e51b..89e36899c5aeb 100644 --- a/frontend/src/scenes/session-recordings/player/playerSettingsLogic.ts +++ b/frontend/src/scenes/session-recordings/player/playerSettingsLogic.ts @@ -1,4 +1,5 @@ -import { actions, connect, kea, path, reducers, selectors } from 'kea' +import { actions, connect, kea, listeners, path, reducers, selectors } from 'kea' +import posthog from 'posthog-js' import { teamLogic } from 'scenes/teamLogic' import { AutoplayDirection, SessionRecordingSidebarStacking } from '~/types' @@ -122,4 +123,13 @@ export const playerSettingsLogic = kea([ (preferredSidebarStacking) => preferredSidebarStacking === SessionRecordingSidebarStacking.Vertical, ], }), + + listeners({ + setSpeed: ({ speed }) => { + posthog.capture('recording player speed changed', { new_speed: speed }) + }, + setSkipInactivitySetting: ({ skipInactivitySetting }) => { + posthog.capture('recording player skip inactivity toggled', { skip_inactivity: skipInactivitySetting }) + }, + }), ]) diff --git a/frontend/src/scenes/session-recordings/player/sessionRecordingDataLogic.ts b/frontend/src/scenes/session-recordings/player/sessionRecordingDataLogic.ts index 10118ce5defdc..ff63bd4b1f397 100644 --- a/frontend/src/scenes/session-recordings/player/sessionRecordingDataLogic.ts +++ b/frontend/src/scenes/session-recordings/player/sessionRecordingDataLogic.ts @@ -1087,13 +1087,13 @@ export const sessionRecordingDataLogic = kea([ if (everyWindowMissingFullSnapshot) { // video is definitely unplayable posthog.capture('recording_has_no_full_snapshot', { - sessionId: sessionRecordingId, + watchedSession: sessionRecordingId, teamId: currentTeam?.id, teamName: currentTeam?.name, }) } else if (anyWindowMissingFullSnapshot) { posthog.capture('recording_window_missing_full_snapshot', { - sessionId: sessionRecordingId, + watchedSession: sessionRecordingId, teamID: currentTeam?.id, teamName: currentTeam?.name, }) @@ -1103,6 +1103,14 @@ export const sessionRecordingDataLogic = kea([ }, ], + isRecentAndInvalid: [ + (s) => [s.start, s.snapshotsInvalid], + (start, snapshotsInvalid) => { + const lessThanFiveMinutesOld = dayjs().diff(start, 'minute') <= 5 + return snapshotsInvalid && lessThanFiveMinutesOld + }, + ], + bufferedToTime: [ (s) => [s.segments], (segments): number | null => { @@ -1160,6 +1168,13 @@ export const sessionRecordingDataLogic = kea([ actions.loadFullEventData(value) } }, + isRecentAndInvalid: (prev: boolean, next: boolean) => { + if (!prev && next) { + posthog.capture('recording cannot playback yet', { + watchedSession: values.sessionPlayerData.sessionRecordingId, + }) + } + }, })), afterMount(({ cache }) => { resetTimingsCache(cache) diff --git a/frontend/src/scenes/session-recordings/player/sessionRecordingPlayerLogic.test.ts b/frontend/src/scenes/session-recordings/player/sessionRecordingPlayerLogic.test.ts index 3dde171f5c309..7a45c26637046 100644 --- a/frontend/src/scenes/session-recordings/player/sessionRecordingPlayerLogic.test.ts +++ b/frontend/src/scenes/session-recordings/player/sessionRecordingPlayerLogic.test.ts @@ -141,7 +141,7 @@ describe('sessionRecordingPlayerLogic', () => { sessionRecordingDataLogic({ sessionRecordingId: '2' }).actionTypes.loadSnapshotSourcesFailure, ]) .toFinishAllListeners() - .toDispatchActions(['setErrorPlayerState']) + .toDispatchActions(['setPlayerError']) expect(logic.values).toMatchObject({ sessionPlayerData: { @@ -149,7 +149,7 @@ describe('sessionRecordingPlayerLogic', () => { snapshotsByWindowId: {}, bufferedToTime: 0, }, - isErrored: true, + playerError: 'loadSnapshotSourcesFailure', }) resumeKeaLoadersErrors() }) diff --git a/frontend/src/scenes/session-recordings/player/sessionRecordingPlayerLogic.ts b/frontend/src/scenes/session-recordings/player/sessionRecordingPlayerLogic.ts index cfda001ed9ea5..5e7e7955f4602 100644 --- a/frontend/src/scenes/session-recordings/player/sessionRecordingPlayerLogic.ts +++ b/frontend/src/scenes/session-recordings/player/sessionRecordingPlayerLogic.ts @@ -1,5 +1,5 @@ import { lemonToast } from '@posthog/lemon-ui' -import { customEvent, EventType, eventWithTime, IncrementalSource } from '@rrweb/types' +import { EventType, eventWithTime, IncrementalSource } from '@rrweb/types' import { captureException } from '@sentry/react' import { actions, @@ -138,12 +138,7 @@ export const sessionRecordingPlayerLogic = kea( playerSettingsLogic, ['setSpeed', 'setSkipInactivitySetting'], eventUsageLogic, - [ - 'reportNextRecordingTriggered', - 'reportRecordingPlayerSkipInactivityToggled', - 'reportRecordingPlayerSpeedChanged', - 'reportRecordingExportedToFile', - ], + ['reportNextRecordingTriggered', 'reportRecordingExportedToFile'], ], })), actions({ @@ -156,7 +151,8 @@ export const sessionRecordingPlayerLogic = kea( endBuffer: true, startScrub: true, endScrub: true, - setErrorPlayerState: (show: boolean) => ({ show }), + setPlayerError: (reason: string) => ({ reason }), + clearPlayerError: true, setSkippingInactivity: (isSkippingInactivity: boolean) => ({ isSkippingInactivity }), syncPlayerSpeed: true, setCurrentTimestamp: (timestamp: number) => ({ timestamp }), @@ -189,7 +185,6 @@ export const sessionRecordingPlayerLogic = kea( // the error is emitted from code we don't control in rrweb, so we can't guarantee it's really an Error playerErrorSeen: (error: any) => ({ error }), fingerprintReported: (fingerprint: string) => ({ fingerprint }), - reportMessageTooLargeWarningSeen: (sessionRecordingId: string) => ({ sessionRecordingId }), setDebugSnapshotTypes: (types: EventType[]) => ({ types }), setDebugSnapshotIncrementalSources: (incrementalSources: IncrementalSource[]) => ({ incrementalSources }), setPlayNextAnimationInterrupted: (interrupted: boolean) => ({ interrupted }), @@ -349,10 +344,7 @@ export const sessionRecordingPlayerLogic = kea( bufferTime: state.bufferTime, } }, - setErrorPlayerState: (state, { show }) => { - if (!show) { - return state - } + setPlayerError: (state) => { return { isPlaying: state.isPlaying, isBuffering: state.isBuffering, @@ -374,7 +366,13 @@ export const sessionRecordingPlayerLogic = kea( }, ], isBuffering: [true, { startBuffer: () => true, endBuffer: () => false }], - isErrored: [false, { setErrorPlayerState: (_, { show }) => show }], + playerError: [ + null as string | null, + { + setPlayerError: (_, { reason }) => (reason.trim().length ? reason : null), + clearPlayerError: () => null, + }, + ], isScrubbing: [false, { startScrub: () => true, endScrub: () => false }], errorCount: [0, { incrementErrorCount: (prevErrorCount) => prevErrorCount + 1 }], @@ -400,12 +398,6 @@ export const sessionRecordingPlayerLogic = kea( setIsFullScreen: (_, { isFullScreen }) => isFullScreen, }, ], - messageTooLargeWarningSeen: [ - null as string | null, - { - reportMessageTooLargeWarningSeen: (_, { sessionRecordingId }) => sessionRecordingId, - }, - ], debugSettings: [ { types: [EventType.FullSnapshot, EventType.IncrementalSnapshot], @@ -431,7 +423,7 @@ export const sessionRecordingPlayerLogic = kea( (s) => [ s.playingState, s.isBuffering, - s.isErrored, + s.playerError, s.isScrubbing, s.isSkippingInactivity, s.snapshotsLoaded, @@ -440,7 +432,7 @@ export const sessionRecordingPlayerLogic = kea( ( playingState, isBuffering, - isErrored, + playerError, isScrubbing, isSkippingInactivity, snapshotsLoaded, @@ -452,7 +444,7 @@ export const sessionRecordingPlayerLogic = kea( return playingState case !snapshotsLoaded && !snapshotsLoading: return SessionPlayerState.READY - case isErrored: + case !!playerError?.trim().length: return SessionPlayerState.ERROR case isSkippingInactivity && playingState !== SessionPlayerState.PAUSE: return SessionPlayerState.SKIP @@ -544,13 +536,6 @@ export const sessionRecordingPlayerLogic = kea( }, ], - messageTooLargeWarnings: [ - (s) => [s.customRRWebEvents], - (customRRWebEvents: customEvent[]) => { - return customRRWebEvents.filter((event) => event.data.tag === 'Message too large') - }, - ], - debugSnapshots: [ (s) => [s.sessionPlayerData, s.debugSettings], (sessionPlayerData: SessionPlayerData, debugSettings): eventWithTime[] => { @@ -672,7 +657,6 @@ export const sessionRecordingPlayerLogic = kea( } }, setSkipInactivitySetting: ({ skipInactivitySetting }) => { - actions.reportRecordingPlayerSkipInactivityToggled(skipInactivitySetting) if (!values.currentSegment?.isActive && skipInactivitySetting) { actions.setSkippingInactivity(true) } else { @@ -784,13 +768,13 @@ export const sessionRecordingPlayerLogic = kea( loadSnapshotsForSourceFailure: () => { if (Object.keys(values.sessionPlayerData.snapshotsByWindowId).length === 0) { console.error('PostHog Recording Playback Error: No snapshots loaded') - actions.setErrorPlayerState(true) + actions.setPlayerError('loadSnapshotsForSourceFailure') } }, loadSnapshotSourcesFailure: () => { if (Object.keys(values.sessionPlayerData.snapshotsByWindowId).length === 0) { console.error('PostHog Recording Playback Error: No snapshots loaded') - actions.setErrorPlayerState(true) + actions.setPlayerError('loadSnapshotSourcesFailure') } }, setPlay: () => { @@ -839,18 +823,17 @@ export const sessionRecordingPlayerLogic = kea( startBuffer: () => { actions.stopAnimation() }, - setErrorPlayerState: ({ show }) => { - if (show) { - actions.incrementErrorCount() - actions.stopAnimation() - } + setPlayerError: () => { + actions.incrementErrorCount() + actions.stopAnimation() }, startScrub: () => { actions.stopAnimation() }, - setSpeed: ({ speed }) => { - actions.reportRecordingPlayerSpeedChanged(speed) - actions.syncPlayerSpeed() + setSpeed: () => { + if (props.mode !== SessionRecordingPlayerMode.Preview) { + actions.syncPlayerSpeed() + } }, seekToTimestamp: ({ timestamp, forcePlay }, breakpoint) => { actions.stopAnimation() @@ -866,25 +849,13 @@ export const sessionRecordingPlayerLogic = kea( // If next time is greater than last buffered time, set to buffering else if (segment?.kind === 'buffer') { - const isStillLoading = values.isRealtimePolling || values.snapshotsLoading - const isPastEnd = values.sessionPlayerData.end && timestamp > values.sessionPlayerData.end.valueOf() - if (isStillLoading) { + const isPastEnd = values.sessionPlayerData.end && timestamp >= values.sessionPlayerData.end.valueOf() + if (isPastEnd) { + actions.setEndReached(true) + } else { values.player?.replayer?.pause() actions.startBuffer() - actions.setErrorPlayerState(false) - } else { - if (isPastEnd) { - actions.setEndReached(true) - } else { - // If not currently loading anything, - // not past the end of the recording, - // and part of the recording hasn't loaded, - // set error state - values.player?.replayer?.pause() - actions.endBuffer() - console.error("Error: Player tried to seek to a position that hasn't loaded yet") - actions.setErrorPlayerState(true) - } + actions.clearPlayerError() } } @@ -895,14 +866,14 @@ export const sessionRecordingPlayerLogic = kea( // can consume 100% CPU and freeze the entire page values.player?.replayer?.pause(values.toRRWebPlayerTime(timestamp)) actions.endBuffer() - actions.setErrorPlayerState(false) + actions.clearPlayerError() } // Otherwise play else { values.player?.replayer?.play(values.toRRWebPlayerTime(timestamp)) actions.updateAnimation() actions.endBuffer() - actions.setErrorPlayerState(false) + actions.clearPlayerError() } breakpoint() @@ -962,7 +933,7 @@ export const sessionRecordingPlayerLogic = kea( // when the buffering progresses values.player?.replayer?.pause() actions.startBuffer() - actions.setErrorPlayerState(false) + actions.clearPlayerError() cache.debug('buffering') return } @@ -1018,7 +989,7 @@ export const sessionRecordingPlayerLogic = kea( cache.pausedMediaElements = values.endReached ? [] : playingElements }, restartIframePlayback: () => { - cache.pausedMediaElements.forEach((el: HTMLMediaElement) => el.play()) + cache.pausedMediaElements?.forEach((el: HTMLMediaElement) => el.play()) cache.pausedMediaElements = [] }, @@ -1107,13 +1078,9 @@ export const sessionRecordingPlayerLogic = kea( await document.exitFullscreen() } }, - - reportMessageTooLargeWarningSeen: async ({ sessionRecordingId }) => { - posthog.capture('message too large warning seen', { sessionRecordingId }) - }, })), - subscriptions(({ actions, values, props }) => ({ + subscriptions(({ actions, values }) => ({ sessionPlayerData: (next, prev) => { const hasSnapshotChanges = next?.snapshotsByWindowId !== prev?.snapshotsByWindowId @@ -1134,13 +1101,15 @@ export const sessionRecordingPlayerLogic = kea( actions.skipPlayerForward(rrwebPlayerTime, values.roughAnimationFPS) } }, - messageTooLargeWarnings: (next) => { - if ( - values.messageTooLargeWarningSeen !== values.sessionRecordingId && - next.length > 0 && - props.mode !== SessionRecordingPlayerMode.Preview - ) { - actions.reportMessageTooLargeWarningSeen(values.sessionRecordingId) + playerError: (next) => { + if (next) { + posthog.capture('recording player error', { + watchedSessionId: values.sessionRecordingId, + currentTimestamp: values.currentTimestamp, + currentSegment: values.currentSegment, + currentPlayerTime: values.currentPlayerTime, + error: next, + }) } }, })), diff --git a/frontend/src/toolbar/debug/EventDebugMenu.tsx b/frontend/src/toolbar/debug/EventDebugMenu.tsx index c7b47688d8f45..747fe146024f4 100644 --- a/frontend/src/toolbar/debug/EventDebugMenu.tsx +++ b/frontend/src/toolbar/debug/EventDebugMenu.tsx @@ -1,4 +1,4 @@ -import { BaseIcon, IconCheck, IconEye, IconLogomark, IconSearch, IconVideoCamera } from '@posthog/icons' +import { BaseIcon, IconCheck, IconEye, IconHide, IconLogomark, IconSearch, IconVideoCamera } from '@posthog/icons' import { useActions, useValues } from 'kea' import { AnimatedCollapsible } from 'lib/components/AnimatedCollapsible' import { PropertyKeyInfo } from 'lib/components/PropertyKeyInfo' @@ -15,10 +15,10 @@ import { EventType } from '~/types' import { ToolbarMenu } from '../bar/ToolbarMenu' -function showEventMenuItem( +function checkableMenuItem( label: string, - count: number, - icon: JSX.Element, + count: number | null, + icon: JSX.Element | null, isActive: boolean, onClick: () => void ): LemonMenuItem { @@ -30,13 +30,15 @@ function showEventMenuItem( {icon} {label}
- - ({count}) - + {count !== null && ( + + ({count}) + + )} ), active: isActive, @@ -70,25 +72,35 @@ export const EventDebugMenu = (): JSX.Element => { searchFilteredEventsCount, expandedEvent, selectedEventTypes, + hidePostHogProperties, + hidePostHogFlags, + expandedProperties, } = useValues(eventDebugMenuLogic) - const { markExpanded, setSelectedEventType, setSearchText, setSearchVisible } = useActions(eventDebugMenuLogic) + const { + markExpanded, + setSelectedEventType, + setSearchText, + setSearchVisible, + setHidePostHogProperties, + setHidePostHogFlags, + } = useActions(eventDebugMenuLogic) const showEventsMenuItems = [ - showEventMenuItem( + checkableMenuItem( 'PostHog Events', searchFilteredEventsCount['posthog'], , selectedEventTypes.includes('posthog'), () => setSelectedEventType('posthog', !selectedEventTypes.includes('posthog')) ), - showEventMenuItem( + checkableMenuItem( 'Custom Events', searchFilteredEventsCount['custom'], , selectedEventTypes.includes('custom'), () => setSelectedEventType('custom', !selectedEventTypes.includes('custom')) ), - showEventMenuItem( + checkableMenuItem( 'Replay Events', searchFilteredEventsCount['snapshot'], , @@ -96,13 +108,23 @@ export const EventDebugMenu = (): JSX.Element => { () => setSelectedEventType('snapshot', !selectedEventTypes.includes('snapshot')) ), ] + + const hideThingsMenuItems = [ + checkableMenuItem('Hide PostHog properties', null, null, hidePostHogProperties, () => + setHidePostHogProperties(!hidePostHogProperties) + ), + checkableMenuItem('Hide PostHog flags', null, null, hidePostHogFlags, () => + setHidePostHogFlags(!hidePostHogFlags) + ), + ] + return (
-
+
View events from this page as they are sent to PostHog.
{ >
@@ -167,7 +189,13 @@ export const EventDebugMenu = (): JSX.Element => {
- + + } + label="Hide properties" + /> ([ eventType, enabled, }), + setHidePostHogProperties: (hide: boolean) => ({ hide }), + setHidePostHogFlags: (hide: boolean) => ({ hide }), }), reducers({ + hidePostHogProperties: [ + false, + { + setHidePostHogProperties: (_, { hide }) => hide, + }, + ], + hidePostHogFlags: [ + false, + { + setHidePostHogFlags: (_, { hide }) => hide, + }, + ], searchVisible: [ false, { @@ -123,6 +137,42 @@ export const eventDebugMenuLogic = kea([ }) }, ], + + expandedProperties: [ + (s) => [s.expandedEvent, s.events, s.hidePostHogProperties, s.hidePostHogFlags], + (expandedEvent, events, hidePostHogProperties, hidePostHogFlags) => { + if (!expandedEvent) { + return [] + } + const theExpandedEvent = events.find((e) => e.uuid === expandedEvent) + if (!theExpandedEvent) { + return [] + } + + const propsFiltered = hidePostHogProperties + ? Object.fromEntries( + Object.entries(theExpandedEvent.properties).filter(([key]) => { + const isPostHogProperty = key.startsWith('$') && PROPERTY_KEYS.includes(key) + const isNonDollarPostHogProperty = CLOUD_INTERNAL_POSTHOG_PROPERTY_KEYS.includes(key) + return !isPostHogProperty && !isNonDollarPostHogProperty + }) + ) + : theExpandedEvent.properties + + return Object.fromEntries( + Object.entries(propsFiltered).filter(([key]) => { + if (hidePostHogFlags) { + if (key === '$active_feature_flags') { + return false + } else if (key.startsWith('$feature/')) { + return false + } + } + return true + }) + ) + }, + ], }), afterMount(({ values, actions }) => { values.posthog?.on('eventCaptured', (e) => {