diff --git a/.github/workflows/rust-docker-build.yml b/.github/workflows/rust-docker-build.yml index 352df97d2d5d7..63a20445e15ee 100644 --- a/.github/workflows/rust-docker-build.yml +++ b/.github/workflows/rust-docker-build.yml @@ -95,7 +95,6 @@ jobs: - name: Container image digest run: echo ${{ steps.docker_build.outputs.digest }} - deploy: name: Deploy capture-replay runs-on: ubuntu-latest diff --git a/frontend/__snapshots__/replay-player-failure--recent-recordings-404--light.png b/frontend/__snapshots__/replay-player-failure--recent-recordings-404--light.png index 9692fb26aa097..cddffb75044b7 100644 Binary files a/frontend/__snapshots__/replay-player-failure--recent-recordings-404--light.png and b/frontend/__snapshots__/replay-player-failure--recent-recordings-404--light.png differ diff --git a/frontend/__snapshots__/replay-player-success--recent-recordings--dark.png b/frontend/__snapshots__/replay-player-success--recent-recordings--dark.png index ad0c127a33fac..82bcb0f5bb0ce 100644 Binary files a/frontend/__snapshots__/replay-player-success--recent-recordings--dark.png and b/frontend/__snapshots__/replay-player-success--recent-recordings--dark.png differ diff --git a/frontend/__snapshots__/replay-player-success--recent-recordings--light.png b/frontend/__snapshots__/replay-player-success--recent-recordings--light.png index b316da52833e9..09792a848277f 100644 Binary files a/frontend/__snapshots__/replay-player-success--recent-recordings--light.png and b/frontend/__snapshots__/replay-player-success--recent-recordings--light.png differ diff --git a/frontend/__snapshots__/replay-player-success--second-recording-in-list--dark.png b/frontend/__snapshots__/replay-player-success--second-recording-in-list--dark.png index 5b169f7d87354..1b8480c6221a3 100644 Binary files a/frontend/__snapshots__/replay-player-success--second-recording-in-list--dark.png and b/frontend/__snapshots__/replay-player-success--second-recording-in-list--dark.png differ diff --git a/frontend/__snapshots__/replay-player-success--second-recording-in-list--light.png b/frontend/__snapshots__/replay-player-success--second-recording-in-list--light.png index f40b8da7ada3e..e406fef79ef8e 100644 Binary files a/frontend/__snapshots__/replay-player-success--second-recording-in-list--light.png and b/frontend/__snapshots__/replay-player-success--second-recording-in-list--light.png differ diff --git a/frontend/src/lib/components/Playlist/Playlist.scss b/frontend/src/lib/components/Playlist/Playlist.scss index d297c5928cb0b..6c4a6d4b9954f 100644 --- a/frontend/src/lib/components/Playlist/Playlist.scss +++ b/frontend/src/lib/components/Playlist/Playlist.scss @@ -51,7 +51,7 @@ .SessionRecordingPlaylistHeightWrapper { // NOTE: Somewhat random way to offset the various headers and tabs above the playlist - height: calc(100vh - 14rem); + height: calc(100vh - 15rem); min-height: 25rem; } diff --git a/frontend/src/lib/lemon-ui/LemonMenu/LemonMenu.tsx b/frontend/src/lib/lemon-ui/LemonMenu/LemonMenu.tsx index 870c0946279ab..71e743d5b6a57 100644 --- a/frontend/src/lib/lemon-ui/LemonMenu/LemonMenu.tsx +++ b/frontend/src/lib/lemon-ui/LemonMenu/LemonMenu.tsx @@ -20,13 +20,15 @@ export interface LemonMenuItemBase custom?: boolean } export interface LemonMenuItemNode extends LemonMenuItemBase { - items: (LemonMenuItemLeaf | false | null)[] + items: (LemonMenuItem | false | null)[] + placement?: LemonDropdownProps['placement'] keyboardShortcut?: never } export type LemonMenuItemLeaf = | (LemonMenuItemBase & { onClick: () => void items?: never + placement?: never keyboardShortcut?: KeyboardShortcut }) | (LemonMenuItemBase & { @@ -34,6 +36,7 @@ export type LemonMenuItemLeaf = disableClientSideRouting?: boolean targetBlank?: boolean items?: never + placement?: never keyboardShortcut?: KeyboardShortcut }) | (LemonMenuItemBase & { @@ -42,6 +45,7 @@ export type LemonMenuItemLeaf = disableClientSideRouting?: boolean targetBlank?: boolean items?: never + placement?: never keyboardShortcut?: KeyboardShortcut }) export interface LemonMenuItemCustom { @@ -52,6 +56,7 @@ export interface LemonMenuItemCustom { keyboardShortcut?: never /** True if the item is a custom element. */ custom?: boolean + placement?: never } export type LemonMenuItem = LemonMenuItemLeaf | LemonMenuItemCustom | LemonMenuItemNode @@ -243,7 +248,7 @@ interface LemonMenuItemButtonProps { const LemonMenuItemButton: FunctionComponent> = React.forwardRef( ( - { item: { label, items, keyboardShortcut, custom, ...buttonProps }, size, tooltipPlacement }, + { item: { label, items, placement, keyboardShortcut, custom, ...buttonProps }, size, tooltipPlacement }, ref ): JSX.Element => { const Label = typeof label === 'function' ? label : null @@ -272,7 +277,7 @@ const LemonMenuItemButton: FunctionComponent diff --git a/frontend/src/scenes/session-recordings/player/PlayerSettings.tsx b/frontend/src/scenes/session-recordings/player/PlayerSettings.tsx new file mode 100644 index 0000000000000..615cda9768b4f --- /dev/null +++ b/frontend/src/scenes/session-recordings/player/PlayerSettings.tsx @@ -0,0 +1,109 @@ +import { IconEllipsis, IconFastForward } from '@posthog/icons' +import { LemonButton, LemonMenu, LemonSelect, LemonSwitch } from '@posthog/lemon-ui' +import clsx from 'clsx' +import { useActions, useValues } from 'kea' + +import { playerSettingsLogic } from './playerSettingsLogic' +import { PLAYBACK_SPEEDS } from './sessionRecordingPlayerLogic' + +export const PlayerSettings = (): JSX.Element => { + const { speed, autoplayDirection, skipInactivitySetting, showMouseTail, showSeekbarTicks } = + useValues(playerSettingsLogic) + const { setSpeed, setAutoplayDirection, setSkipInactivitySetting, setShowMouseTail, setShowSeekbarTicks } = + useActions(playerSettingsLogic) + + return ( +
+ ( +
+ Autoplay + + + Autoplay next recording +
({!autoplayDirection ? 'off' : autoplayDirection}) +
+ } + value={autoplayDirection} + aria-label="Autoplay next recording" + onChange={setAutoplayDirection} + dropdownPlacement="bottom-end" + dropdownMatchSelectWidth={false} + options={[ + { value: null, label: 'Off' }, + { value: 'newer', label: 'Newer recordings' }, + { value: 'older', label: 'Older recordings' }, + ]} + size="small" + /> +
+ ), + }, + { + custom: true, + label: () => ( + + ), + }, + { + custom: true, + label: () => ( + + ), + }, + { + custom: true, + label: () => ( + + } + fullWidth + data-attr="skip-inactivity" + className="px-2 py-1" + checked={skipInactivitySetting} + onChange={setSkipInactivitySetting} + label="Skip inactivity" + /> + ), + }, + { + label: `Playback speed (${speed}x)`, + items: PLAYBACK_SPEEDS.map((speedToggle) => ({ + label: `${speedToggle}x`, + onClick: () => setSpeed(speedToggle), + active: speedToggle === speed, + })), + placement: 'right-end', + }, + ]} + > + } /> + + + ) +} diff --git a/frontend/src/scenes/session-recordings/player/controller/PlayerController.tsx b/frontend/src/scenes/session-recordings/player/controller/PlayerController.tsx index 061ffee0c56cf..96e27ef39df1d 100644 --- a/frontend/src/scenes/session-recordings/player/controller/PlayerController.tsx +++ b/frontend/src/scenes/session-recordings/player/controller/PlayerController.tsx @@ -1,20 +1,15 @@ -import { IconFastForward, IconPause, IconPlay } from '@posthog/icons' -import { LemonMenu, LemonSwitch } from '@posthog/lemon-ui' +import { IconPause, IconPlay } from '@posthog/icons' import clsx from 'clsx' import { useActions, useValues } from 'kea' import { IconFullScreen, IconSync } from 'lib/lemon-ui/icons' import { LemonButton } from 'lib/lemon-ui/LemonButton' -import { Tooltip } from 'lib/lemon-ui/Tooltip' -import { - PLAYBACK_SPEEDS, - sessionRecordingPlayerLogic, -} from 'scenes/session-recordings/player/sessionRecordingPlayerLogic' +import { sessionRecordingPlayerLogic } from 'scenes/session-recordings/player/sessionRecordingPlayerLogic' import { KeyboardShortcut } from '~/layout/navigation-3000/components/KeyboardShortcut' import { SessionPlayerState } from '~/types' import { PlayerMetaLinks } from '../PlayerMetaLinks' -import { playerSettingsLogic } from '../playerSettingsLogic' +import { PlayerSettings } from '../PlayerSettings' import { SeekSkip, Timestamp } from './PlayerControllerTime' import { Seekbar } from './Seekbar' @@ -22,9 +17,6 @@ export function PlayerController({ linkIconsOnly }: { linkIconsOnly: boolean }): const { playingState, isFullScreen, endReached } = useValues(sessionRecordingPlayerLogic) const { togglePlayPause, setIsFullScreen } = useActions(sessionRecordingPlayerLogic) - const { speed, skipInactivitySetting } = useValues(playerSettingsLogic) - const { setSpeed, setSkipInactivitySetting } = useActions(playerSettingsLogic) - const showPause = playingState === SessionPlayerState.PLAY return ( @@ -54,41 +46,17 @@ export function PlayerController({ linkIconsOnly }: { linkIconsOnly: boolean }): - ({ - label: `${speedToggle}x`, - onClick: () => setSpeed(speedToggle), - }))} + setIsFullScreen(!isFullScreen)} + tooltip={`${!isFullScreen ? 'Go' : 'Exit'} full screen (F)`} > - - {speed}x - - - - } - /> - -
- - setIsFullScreen(!isFullScreen)}> - - - + +
+ diff --git a/frontend/src/scenes/session-recordings/player/inspector/playerInspectorLogic.ts b/frontend/src/scenes/session-recordings/player/inspector/playerInspectorLogic.ts index a212523f397e5..110c277aa8743 100644 --- a/frontend/src/scenes/session-recordings/player/inspector/playerInspectorLogic.ts +++ b/frontend/src/scenes/session-recordings/player/inspector/playerInspectorLogic.ts @@ -152,7 +152,7 @@ export const playerInspectorLogic = kea([ ], values: [ playerSettingsLogic, - ['showOnlyMatching', 'tab', 'miniFiltersByKey', 'searchQuery'], + ['showOnlyMatching', 'showSeekbarTicks', 'tab', 'miniFiltersByKey', 'searchQuery'], sessionRecordingDataLogic(props), [ 'sessionPlayerData', @@ -575,13 +575,17 @@ export const playerInspectorLogic = kea([ ], seekbarItems: [ - (s) => [s.allItems, s.showOnlyMatching, s.showMatchingEventsFilter], - (allItems, showOnlyMatching, showMatchingEventsFilter): InspectorListItemEvent[] => { + (s) => [s.allItems, s.showOnlyMatching, s.showSeekbarTicks, s.showMatchingEventsFilter], + (allItems, showOnlyMatching, showSeekbarTicks, showMatchingEventsFilter): InspectorListItemEvent[] => { let items = allItems.filter((item) => { if (item.type !== SessionRecordingPlayerTab.EVENTS) { return false } + if (!showSeekbarTicks && ['$pageview', '$screen'].includes(item.data.event)) { + return false + } + return !(showMatchingEventsFilter && showOnlyMatching && item.highlightColor !== 'primary') }) as InspectorListItemEvent[] diff --git a/frontend/src/scenes/session-recordings/player/playerSettingsLogic.ts b/frontend/src/scenes/session-recordings/player/playerSettingsLogic.ts index 7de31840ebca2..96021bb74ba16 100644 --- a/frontend/src/scenes/session-recordings/player/playerSettingsLogic.ts +++ b/frontend/src/scenes/session-recordings/player/playerSettingsLogic.ts @@ -203,6 +203,8 @@ export const playerSettingsLogic = kea([ setTimestampFormat: (format: TimestampFormat) => ({ format }), setPreferredInspectorStacking: (stacking: InspectorStacking) => ({ stacking }), setPlaybackViewMode: (mode: PlaybackViewMode) => ({ mode }), + setShowMouseTail: (showMouseTail: boolean) => ({ showMouseTail }), + setShowSeekbarTicks: (show: boolean) => ({ show }), }), connect({ values: [teamLogic, ['currentTeam']], @@ -289,6 +291,20 @@ export const playerSettingsLogic = kea([ setHideViewedRecordings: (_, { hideViewedRecordings }) => hideViewedRecordings, }, ], + showMouseTail: [ + true, + { persist: true }, + { + setShowMouseTail: (_, { showMouseTail }) => showMouseTail, + }, + ], + showSeekbarTicks: [ + true, + { persist: true }, + { + setShowSeekbarTicks: (_, { show }) => show, + }, + ], // Inspector tab: [ diff --git a/frontend/src/scenes/session-recordings/player/sessionRecordingPlayerLogic.ts b/frontend/src/scenes/session-recordings/player/sessionRecordingPlayerLogic.ts index 8dfeb641055ff..dd56272db691a 100644 --- a/frontend/src/scenes/session-recordings/player/sessionRecordingPlayerLogic.ts +++ b/frontend/src/scenes/session-recordings/player/sessionRecordingPlayerLogic.ts @@ -113,7 +113,7 @@ export const sessionRecordingPlayerLogic = kea( 'fullyLoaded', ], playerSettingsLogic, - ['speed', 'skipInactivitySetting'], + ['speed', 'skipInactivitySetting', 'showMouseTail'], userLogic, ['user', 'hasAvailableFeature'], preflightLogic, @@ -549,7 +549,7 @@ export const sessionRecordingPlayerLogic = kea( ...COMMON_REPLAYER_CONFIG, // these two settings are attempts to improve performance of running two Replayers at once // the main player and a preview player - mouseTail: props.mode !== SessionRecordingPlayerMode.Preview, + mouseTail: values.showMouseTail && props.mode !== SessionRecordingPlayerMode.Preview, useVirtualDom: false, plugins, onError: (error) => { diff --git a/frontend/src/scenes/session-recordings/playlist/SessionRecordingsPlaylistSettings.tsx b/frontend/src/scenes/session-recordings/playlist/SessionRecordingsPlaylistSettings.tsx index 972628dd0032b..007319a686f45 100644 --- a/frontend/src/scenes/session-recordings/playlist/SessionRecordingsPlaylistSettings.tsx +++ b/frontend/src/scenes/session-recordings/playlist/SessionRecordingsPlaylistSettings.tsx @@ -1,44 +1,17 @@ -import { LemonSelect, LemonSwitch } from '@posthog/lemon-ui' +import { LemonSwitch } from '@posthog/lemon-ui' import { useActions, useValues } from 'kea' -import { Tooltip } from 'lib/lemon-ui/Tooltip' import { DurationTypeSelect } from 'scenes/session-recordings/filters/DurationTypeSelect' import { playerSettingsLogic } from '../player/playerSettingsLogic' import { sessionRecordingsPlaylistLogic } from './sessionRecordingsPlaylistLogic' export function SessionRecordingsPlaylistSettings(): JSX.Element { - const { autoplayDirection, durationTypeToShow, hideViewedRecordings } = useValues(playerSettingsLogic) - const { setAutoplayDirection, setDurationTypeToShow, setHideViewedRecordings } = useActions(playerSettingsLogic) + const { durationTypeToShow, hideViewedRecordings } = useValues(playerSettingsLogic) + const { setDurationTypeToShow, setHideViewedRecordings } = useActions(playerSettingsLogic) const { orderBy } = useValues(sessionRecordingsPlaylistLogic) return (
- - Autoplay next recording -
({!autoplayDirection ? 'off' : autoplayDirection}) -
- } - placement="right" - > -
- Autoplay - - -
-
Hide viewed