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

wip: new replay panels #26698

Draft
wants to merge 42 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
cbb2601
its a start
daibhin Nov 30, 2024
bec3d46
cleaner but not working
daibhin Nov 30, 2024
550c728
very good
daibhin Nov 30, 2024
e85ac2b
good layout
daibhin Nov 30, 2024
8ea3040
better but without container queries
daibhin Nov 30, 2024
44accd2
container query
daibhin Dec 1, 2024
0438f66
container query names
daibhin Dec 2, 2024
a91f3ce
container queries wip
daibhin Dec 2, 2024
cfef01d
rough layout
daibhin Dec 2, 2024
90712ef
Merge branch 'master' into dn-feat/flexing-panels
daibhin Dec 2, 2024
d452eca
coalesce
daibhin Dec 5, 2024
e16d444
Merge branch 'master' into dn-feat/flexing-panels
daibhin Dec 5, 2024
1d28908
mobile layout
daibhin Dec 5, 2024
9e967ff
Update UI snapshots for `chromium` (1)
github-actions[bot] Dec 5, 2024
9ad7b51
Update UI snapshots for `chromium` (2)
github-actions[bot] Dec 5, 2024
1028ac3
filters
daibhin Dec 6, 2024
64b3fd9
fix types
daibhin Dec 6, 2024
8cebb36
Merge branch 'dn-feat/flexing-panels' of github.com:PostHog/posthog i…
daibhin Dec 6, 2024
0b1d124
Update UI snapshots for `chromium` (1)
github-actions[bot] Dec 6, 2024
9e4974e
Update UI snapshots for `chromium` (2)
github-actions[bot] Dec 6, 2024
5850c22
session overview panel
daibhin Dec 6, 2024
7508fa3
scrolling
daibhin Dec 6, 2024
20f95a2
Merge branch 'dn-feat/flexing-panels' of github.com:PostHog/posthog i…
daibhin Dec 6, 2024
9c29b9f
Update UI snapshots for `chromium` (1)
github-actions[bot] Dec 6, 2024
9dbf3ee
Update UI snapshots for `chromium` (2)
github-actions[bot] Dec 6, 2024
aeba320
workspace config example
daibhin Dec 9, 2024
1d7ae25
Merge branch 'dn-feat/flexing-panels' of github.com:PostHog/posthog i…
daibhin Dec 9, 2024
445bd0b
Update UI snapshots for `chromium` (1)
github-actions[bot] Dec 9, 2024
c4978fe
Update UI snapshots for `chromium` (2)
github-actions[bot] Dec 9, 2024
e7adc57
Merge branch 'master' into dn-feat/flexing-panels
pauldambra Dec 14, 2024
c76bbed
behind a flag
pauldambra Dec 14, 2024
1f8a915
Update UI snapshots for `chromium` (1)
github-actions[bot] Dec 14, 2024
74cc87f
Update UI snapshots for `chromium` (2)
github-actions[bot] Dec 14, 2024
7543baa
Update UI snapshots for `chromium` (1)
github-actions[bot] Dec 14, 2024
7c0d7b1
Update UI snapshots for `chromium` (2)
github-actions[bot] Dec 14, 2024
fa79f86
maybe this
pauldambra Dec 14, 2024
850b461
Update UI snapshots for `chromium` (2)
github-actions[bot] Dec 14, 2024
93e6d78
maybe story
pauldambra Dec 15, 2024
fd3ff7b
Update UI snapshots for `chromium` (2)
github-actions[bot] Dec 15, 2024
cbbfd10
Merge branch 'master' into dn-feat/flexing-panels
pauldambra Dec 23, 2024
babb342
Merge branch 'master' into dn-feat/flexing-panels
pauldambra Jan 5, 2025
be29b94
stuff
pauldambra Jan 5, 2025
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
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 3 additions & 1 deletion frontend/src/lib/components/DateFilter/DateFilter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export interface DateFilterProps {
dateOptions?: DateMappingOption[]
isDateFormatted?: boolean
size?: LemonButtonProps['size']
type?: LemonButtonProps['type']
dropdownPlacement?: Placement
/* True when we're not dealing with ranges, but a single date / relative date */
isFixedDateMode?: boolean
Expand All @@ -60,6 +61,7 @@ export function DateFilter({
dateOptions = dateMapping,
isDateFormatted = true,
size,
type,
dropdownPlacement = 'bottom-start',
max,
isFixedDateMode = false,
Expand Down Expand Up @@ -242,7 +244,7 @@ export function DateFilter({
<LemonButton
id="daterange_selector"
size={size ?? 'small'}
type="secondary"
type={type ?? 'secondary'}
disabledReason={disabledReason}
data-attr="date-filter"
icon={<IconCalendar />}
Expand Down
1 change: 1 addition & 0 deletions frontend/src/lib/constants.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,7 @@ export const FEATURE_FLAGS = {
CDP_ACTIVITY_LOG_NOTIFICATIONS: 'cdp-activity-log-notifications', // owner: #team-cdp
COOKIELESS_SERVER_HASH_MODE_SETTING: 'cookieless-server-hash-mode-setting', // owner: @robbie-c #team-web-analytics
INSIGHT_COLORS: 'insight-colors', // owner @thmsobrmlr #team-product-analytics
SESSION_REPLAY_PANELS_UI: 'session-replay-panels-ui', // owner: @pauldambra #team-replay
} as const
export type FeatureFlagKey = (typeof FEATURE_FLAGS)[keyof typeof FEATURE_FLAGS]

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { createPostHogWidgetNode } from 'scenes/notebooks/Nodes/NodeWrapper'
import { FilterType, NotebookNodeType, RecordingUniversalFilters, ReplayTabs } from '~/types'
import {
DEFAULT_RECORDING_FILTERS,
SessionRecordingPlaylistLogicProps,
convertLegacyFiltersToUniversalFilters,
sessionRecordingsPlaylistLogic,
} from 'scenes/session-recordings/playlist/sessionRecordingsPlaylistLogic'
Expand All @@ -17,6 +16,7 @@ import { sessionRecordingPlayerLogic } from 'scenes/session-recordings/player/se
import { IconComment } from 'lib/lemon-ui/icons'
import { sessionRecordingPlayerLogicType } from 'scenes/session-recordings/player/sessionRecordingPlayerLogicType'
import { RecordingsUniversalFilters } from 'scenes/session-recordings/filters/RecordingsUniversalFilters'
import { SessionRecordingPlaylistLogicProps } from 'scenes/session-recordings/types'

const Component = ({
attributes,
Expand Down
78 changes: 78 additions & 0 deletions frontend/src/scenes/session-recordings/PanelUI.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import './SessionReplay.scss'

import clsx from 'clsx'
import { useValues } from 'kea'
import { sessionRecordingsPlaylistLogic } from 'scenes/session-recordings/playlist/sessionRecordingsPlaylistLogic'

import { PanelFilters } from './panels/Filters'
import { PanelOverview } from './panels/Overview'
import { PanelPlayback } from './panels/Playback'
import { PanelPlaylist } from './panels/Playlist'
import { PlayerInspector } from './player/inspector/PlayerInspector'

export function PanelsUI(): JSX.Element {
const workspaceConfig = { overview: false, inspector: true }

const { activeSessionRecordingId } = useValues(sessionRecordingsPlaylistLogic({ updateSearchParams: true }))

return (
<PanelLayout className="SessionReplay__layout">
<PanelContainer primary={false} className="PanelLayout__secondary flex-col">
<Panel primary={false}>
<PanelFilters />
</Panel>
<Panel primary className="PanelLayout__playlist overflow-y-auto flex-1 border w-full">
<PanelPlaylist />
</Panel>
</PanelContainer>

<PanelContainer primary className="PanelLayout__primary">
{workspaceConfig.overview && (
<PanelContainer primary={false} className="w-full">
<Panel primary className="PanelLayout__overview">
<PanelOverview />
</Panel>
</PanelContainer>
)}
<PanelContainer primary className="w-full PanelLayout__main">
<Panel primary className="PanelLayout__playback">
<PanelPlayback />
</Panel>
{workspaceConfig.inspector && (
<Panel primary={false} className="PanelLayout__inspector flex flex-col">
{/*TODO: this only works because we're not using a playerkey yet*/}
<PlayerInspector sessionRecordingId={activeSessionRecordingId} />
</Panel>
)}
</PanelContainer>
</PanelContainer>
</PanelLayout>
)
}

function PanelLayout(props: Omit<PanelContainerProps, 'primary'>): JSX.Element {
return <PanelContainer {...props} primary={false} />
}

type PanelContainerProps = {
children: React.ReactNode
primary: boolean
className?: string
}

function PanelContainer({ children, primary, className }: PanelContainerProps): JSX.Element {
return <div className={clsx('flex flex-wrap gap-2', primary && 'flex-1', className)}>{children}</div>
}

function Panel({
className,
primary,
children,
}: {
className?: string
primary: boolean
collapsed?: boolean
children: JSX.Element
}): JSX.Element {
return <div className={clsx(primary && 'flex-1', 'border', className)}>{children}</div>
}
15 changes: 12 additions & 3 deletions frontend/src/scenes/session-recordings/SessionRecordings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
AuthorizedUrlListType,
defaultAuthorizedUrlProperties,
} from 'lib/components/AuthorizedUrlList/authorizedUrlListLogic'
import { FlaggedFeature } from 'lib/components/FlaggedFeature'
import { PageHeader } from 'lib/components/PageHeader'
import { upgradeModalLogic } from 'lib/components/UpgradeModal/upgradeModalLogic'
import { VersionCheckerBanner } from 'lib/components/VersionChecker/VersionCheckerBanner'
Expand All @@ -26,6 +27,7 @@ import { urls } from 'scenes/urls'
import { sidePanelSettingsLogic } from '~/layout/navigation-3000/sidepanel/panels/sidePanelSettingsLogic'
import { AvailableFeature, NotebookNodeType, ReplayTabs } from '~/types'

import { PanelsUI } from './PanelUI'
import { createPlaylist } from './playlist/playlistUtils'
import { SessionRecordingsPlaylist } from './playlist/SessionRecordingsPlaylist'
import { SavedSessionRecordingPlaylists } from './saved-playlists/SavedSessionRecordingPlaylists'
Expand Down Expand Up @@ -195,9 +197,16 @@ function MainPanel(): JSX.Element {
{!tab ? (
<Spinner />
) : tab === ReplayTabs.Home ? (
<div className="SessionRecordingPlaylistHeightWrapper">
<SessionRecordingsPlaylist updateSearchParams />
</div>
<>
<FlaggedFeature flag={FEATURE_FLAGS.SESSION_REPLAY_PANELS_UI} match={true}>
<PanelsUI />
</FlaggedFeature>
<FlaggedFeature flag={FEATURE_FLAGS.SESSION_REPLAY_PANELS_UI} match={false}>
<div className="SessionRecordingPlaylistHeightWrapper">
<SessionRecordingsPlaylist updateSearchParams />
</div>
</FlaggedFeature>
</>
) : tab === ReplayTabs.Playlists ? (
<SavedSessionRecordingPlaylists tab={ReplayTabs.Playlists} />
) : tab === ReplayTabs.Templates ? (
Expand Down
101 changes: 101 additions & 0 deletions frontend/src/scenes/session-recordings/SessionReplay.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
.SessionReplay__layout {
--session-recordings-tabs-height: 62px;
--session-recordings-panel-height: calc(
100vh - var(--breadcrumbs-height-full) - var(--scene-padding) - var(--scene-padding-bottom) -
var(--session-recordings-tabs-height)
);
--panel-gap: 0.5rem;
--panel-playlist-min-width: 350px;
--panel-filters-min-width: 350px;
--panel-playback-min-width: 300px;
--panel-inspector-min-width: 200px;
--container-primary-min-width: max(var(--panel-playback-min-width), var(--panel-inspector-min-width));
--container-secondary-min-width: max(var(--panel-playlist-min-width), var(--panel-filters-min-width));
--container-secondary-max-width: 200px;

height: var(--session-recordings-panel-height);
container: replay-panel-layout / inline-size;

.PanelLayout__primary {
container: replay-primary-panels / inline-size;
flex: 1 1 0% !important;
min-width: var(--container-primary-min-width);
}

.PanelLayout__playlist {
flex: 1 1 0% !important;
min-width: var(--panel-playlist-min-width);
}

.PanelLayout__playback {
flex: 1 1 0% !important;
min-width: var(--panel-playback-min-width);
min-height: 300px;
}

.PanelLayout__inspector {
min-width: var(--panel-inspector-min-width);
}

.UniversalFilterButton {
height: 1.75rem;
font-size: smaller;
}

.PanelLayout__Panel {
&--collapsed {
width: 40px;
min-width: unset;
height: 40px;
}
}

// All primary panels horizontally stacked
// var(--panel-playback-min-width) + var(--panel-inspector-min-width) + var(--panel-gap) + var(--panel-gap)
@container replay-panel-layout (width >= 1040px) {
.PanelLayout__inspector {
height: 100%;
overflow-y: auto;
}

.PanelLayout__primary {
flex-direction: column;
}
}

// Primary panels vertically stacked
// var(--panel-playback-min-width) + var(--panel-inspector-min-width) + var(--panel-gap) + var(--panel-gap)
@container replay-panel-layout (width < 1040px) {
.PanelLayout__inspector {
width: 100%;
}
}

// Layout vertically stacked
@container replay-panel-layout (width < 508px) {
// var(--container-primary-min-width) + var(--container-secondary-min-width) + var(--panel-gap)
.PanelLayout__secondary {
width: 100%;
}

.PanelLayout__playlist {
max-height: 150px;
}
}

@container replay-panel-layout (width > 508px) {
// var(--container-primary-min-width) + var(--container-secondary-min-width) + var(--panel-gap)
.PanelLayout__secondary {
min-width: var(--container-secondary-min-width);
max-width: var(--container-secondary-max-width);
height: 100%;
}
}

@container replay-panel-layout (508px < width < 1040px) {
.PanelLayout__primary {
height: 100%;
overflow-y: auto;
}
}
}
10 changes: 3 additions & 7 deletions frontend/src/scenes/session-recordings/apm/networkViewLogic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,13 @@ import { actions, afterMount, connect, kea, key, path, props, reducers, selector
import { humanFriendlyMilliseconds } from 'lib/utils'
import { performanceEventDataLogic } from 'scenes/session-recordings/apm/performanceEventDataLogic'
import { percentagesWithinEventRange } from 'scenes/session-recordings/apm/waterfall/TimingBar'
import {
sessionRecordingDataLogic,
SessionRecordingDataLogicProps,
} from 'scenes/session-recordings/player/sessionRecordingDataLogic'
import { sessionRecordingDataLogic } from 'scenes/session-recordings/player/sessionRecordingDataLogic'
import { NetworkViewLogicProps } from 'scenes/session-recordings/types'

import { PerformanceEvent } from '~/types'

import type { networkViewLogicType } from './networkViewLogicType'

export interface NetworkViewLogicProps extends SessionRecordingDataLogicProps {}

export const networkViewLogic = kea<networkViewLogicType>([
path(['scenes', 'session-recordings', 'apm', 'networkViewLogic']),
key((props) => `network-view-${props.sessionRecordingId}`),
Expand All @@ -21,7 +17,7 @@ export const networkViewLogic = kea<networkViewLogicType>([
values: [
sessionRecordingDataLogic(props),
['sessionPlayerData', 'sessionPlayerMetaData', 'snapshotsLoading', 'sessionPlayerMetaDataLoading'],
performanceEventDataLogic({ key: props.sessionRecordingId, sessionRecordingId: props.sessionRecordingId }),
performanceEventDataLogic(props),
['allPerformanceEvents', 'sizeBreakdown'],
],
actions: [sessionRecordingDataLogic(props), ['loadSnapshots', 'maybeLoadRecordingMeta']],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,8 @@ import {
itemSizeInfo,
} from 'scenes/session-recordings/apm/performance-event-utils'
import { InspectorListItemBase } from 'scenes/session-recordings/player/inspector/playerInspectorLogic'
import {
sessionRecordingDataLogic,
SessionRecordingDataLogicProps,
} from 'scenes/session-recordings/player/sessionRecordingDataLogic'
import { sessionRecordingDataLogic } from 'scenes/session-recordings/player/sessionRecordingDataLogic'
import { PerformanceEventDataLogicProps } from 'scenes/session-recordings/types'

import { FilterableInspectorListItemTypes, PerformanceEvent, RecordingEventType } from '~/types'

Expand All @@ -19,10 +17,6 @@ export type InspectorListItemPerformance = InspectorListItemBase & {
data: PerformanceEvent
}

export interface PerformanceEventDataLogicProps extends SessionRecordingDataLogicProps {
key?: string
}

/** it's pretty quick to sort an already sorted list */
function sortPerformanceEvents(events: PerformanceEvent[]): PerformanceEvent[] {
return events.sort((a, b) => (a.timestamp.valueOf() > b.timestamp.valueOf() ? 1 : -1))
Expand Down Expand Up @@ -101,7 +95,7 @@ function matchWebVitalsEvents(
export const performanceEventDataLogic = kea<performanceEventDataLogicType>([
path(['scenes', 'session-recordings', 'apm', 'performanceEventDataLogic']),
props({} as PerformanceEventDataLogicProps),
key((props: PerformanceEventDataLogicProps) => `${props.key}-${props.sessionRecordingId}`),
key((props: PerformanceEventDataLogicProps) => props.sessionRecordingId),
connect((props: PerformanceEventDataLogicProps) => ({
actions: [],
values: [sessionRecordingDataLogic(props), ['sessionPlayerData', 'webVitalsEvents']],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export function SettingsBar({
className={clsx(
border === 'bottom' && 'border-b',
border === 'top' && 'border-t',
'flex flex-row w-full overflow-hidden font-light text-xs bg-bg-3000 items-center',
'flex flex-row w-full overflow-hidden font-light text-xs bg-bg-3000 items-center overflow-x-auto',
className
)}
>
Expand Down
16 changes: 12 additions & 4 deletions frontend/src/scenes/session-recordings/filters/DurationFilter.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { LemonButton } from '@posthog/lemon-ui'
import { LemonButton, LemonButtonProps } from '@posthog/lemon-ui'
import { convertSecondsToDuration, DurationPicker } from 'lib/components/DurationPicker/DurationPicker'
import { OperatorSelect } from 'lib/components/PropertyFilters/components/OperatorValueSelect'
import { Popover } from 'lib/lemon-ui/Popover/Popover'
Expand All @@ -12,6 +12,8 @@ interface Props {
durationTypeFilter: DurationType
onChange: (recordingDurationFilter: RecordingDurationFilter, durationType: DurationType) => void
pageKey: string
size?: LemonButtonProps['size']
type?: LemonButtonProps['type']
}

const durationTypeMapping: Record<DurationType, string> = {
Expand All @@ -31,7 +33,13 @@ export const humanFriendlyDurationFilter = (
return `${operator} ${duration.timeValue || 0} ${durationDescription}${unit}`
}

export function DurationFilter({ recordingDurationFilter, durationTypeFilter, onChange }: Props): JSX.Element {
export function DurationFilter({
recordingDurationFilter,
durationTypeFilter,
onChange,
size,
type,
}: Props): JSX.Element {
const [isOpen, setIsOpen] = useState(false)
const durationString = useMemo(
() => humanFriendlyDurationFilter(recordingDurationFilter, durationTypeFilter),
Expand Down Expand Up @@ -69,8 +77,8 @@ export function DurationFilter({ recordingDurationFilter, durationTypeFilter, on
}
>
<LemonButton
type="secondary"
size="small"
type={type ?? 'secondary'}
size={size ?? 'small'}
onClick={() => {
setIsOpen(true)
}}
Expand Down
Loading