Skip to content

Commit

Permalink
feat: replay playlist 4000 take 1 (#26449)
Browse files Browse the repository at this point in the history
Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: David Newell <[email protected]>
  • Loading branch information
3 people authored Nov 27, 2024
1 parent 405c457 commit 7daba61
Show file tree
Hide file tree
Showing 30 changed files with 336 additions and 245 deletions.
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.
Binary file modified frontend/__snapshots__/components-playlist--default--dark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified frontend/__snapshots__/components-playlist--default--light.png
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.
Binary file modified frontend/__snapshots__/components-playlist--with-footer--dark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified frontend/__snapshots__/components-playlist--with-footer--light.png
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.
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.
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.
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.
137 changes: 66 additions & 71 deletions frontend/src/lib/components/Playlist/Playlist.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import './Playlist.scss'

import { IconCollapse } from '@posthog/icons'
import { LemonButton, LemonButtonProps, LemonCollapse, LemonSkeleton, Tooltip } from '@posthog/lemon-ui'
import { LemonButton, LemonCollapse, LemonSkeleton, Tooltip } from '@posthog/lemon-ui'
import clsx from 'clsx'
import { useResizeBreakpoints } from 'lib/hooks/useResizeObserver'
import { IconChevronRight } from 'lib/lemon-ui/icons'
import { LemonTableLoader } from 'lib/lemon-ui/LemonTable/LemonTableLoader'
import { range } from 'lib/utils'
import { useEffect, useRef, useState } from 'react'
import { useRef, useState } from 'react'
import { DraggableToNotebook } from 'scenes/notebooks/AddToNotebook/DraggableToNotebook'

import { Resizer } from '../Resizer/Resizer'
Expand All @@ -23,11 +23,6 @@ export type PlaylistSection<T> = {
footer?: JSX.Element
}

type PlaylistHeaderAction = Pick<LemonButtonProps, 'icon' | 'tooltip' | 'children'> & {
key: string
content: React.ReactNode
}

export type PlaylistProps<T> = {
sections: PlaylistSection<T>[]
listEmptyState: JSX.Element
Expand All @@ -36,19 +31,32 @@ export type PlaylistProps<T> = {
notebooksHref?: string
embedded?: boolean
loading?: boolean
headerActions?: PlaylistHeaderAction[]
headerActions?: JSX.Element
footerActions?: JSX.Element
onScrollListEdge?: (edge: 'top' | 'bottom') => void
// Optionally select the first item in the list. Only works in controlled mode
selectInitialItem?: boolean
onSelect?: (item: T) => void
onChangeSections?: (activeKeys: string[]) => void
'data-attr'?: string
activeItemId?: string
controls?: JSX.Element | null
}

const CounterBadge = ({ children }: { children: React.ReactNode }): JSX.Element => (
<span className="rounded py-1 px-2 mr-1 text-xs bg-border-light font-semibold select-none">{children}</span>
const CounterBadge = ({
children,
size = 'small',
}: {
children: React.ReactNode
size?: 'small' | 'xsmall'
}): JSX.Element => (
<span
className={clsx(
'rounded py-1 px-2 bg-border-light font-semibold select-none',
size === 'small' ? 'text-xs' : 'text-xxs'
)}
>
{children}
</span>
)

export function Playlist<
Expand All @@ -64,14 +72,14 @@ export function Playlist<
activeItemId: propsActiveItemId,
content,
sections,
headerActions = [],
headerActions,
footerActions,
onScrollListEdge,
listEmptyState,
selectInitialItem,
onSelect,
onChangeSections,
'data-attr': dataAttr,
controls,
}: PlaylistProps<T>): JSX.Element {
const [controlledActiveItemId, setControlledActiveItemId] = useState<T['id'] | null>(
selectInitialItem && sections[0].items[0] ? sections[0].items[0].id : null
Expand Down Expand Up @@ -111,13 +119,13 @@ export function Playlist<
loading={loading}
sections={sections}
headerActions={headerActions}
footerActions={footerActions}
onScrollListEdge={onScrollListEdge}
onClickCollapse={() => setListCollapsed(true)}
activeItemId={activeItemId}
setActiveItemId={onChangeActiveItem}
onChangeSections={onChangeSections}
emptyState={listEmptyState}
controls={controls}
/>
)}
<Resizer
Expand All @@ -136,10 +144,42 @@ export function Playlist<

const CollapsedList = ({ onClickOpen }: { onClickOpen: () => void }): JSX.Element => (
<div className="flex items-start h-full bg-bg-light border-r p-1">
<LemonButton size="small" icon={<IconChevronRight />} onClick={onClickOpen} />
<LemonButton size="xsmall" icon={<IconChevronRight />} onClick={onClickOpen} />
</div>
)

function TitleWithCount({
title,
count,
onClickCollapse,
}: {
title?: string
count: number
onClickCollapse: () => void
}): JSX.Element {
return (
<div className="flex items-center gap-0.5">
<LemonButton size="xsmall" icon={<IconCollapse className="rotate-90" />} onClick={onClickCollapse} />
<span className="flex flex-1 gap-1 items-center">
{title ? <span className="font-bold uppercase text-xxs tracking-wide">{title}</span> : null}
<Tooltip
placement="bottom"
title={
<>
Showing {count} results.
<br />
Scrolling to the bottom or the top of the list will load older or newer results
respectively.
</>
}
>
<CounterBadge size="xsmall">{Math.min(999, count)}+</CounterBadge>
</Tooltip>
</span>
</div>
)
}

function List<
T extends {
id: string | number
Expand All @@ -150,38 +190,31 @@ function List<
notebooksHref,
onClickCollapse,
setActiveItemId,
headerActions = [],
headerActions,
footerActions,
sections,
onChangeSections,
activeItemId,
onScrollListEdge,
loading,
emptyState,
controls,
}: {
title: PlaylistProps<T>['title']
title?: string
notebooksHref: PlaylistProps<T>['notebooksHref']
onClickCollapse: () => void
activeItemId: T['id'] | null
setActiveItemId: (item: T) => void
headerActions: PlaylistProps<T>['headerActions']
footerActions: PlaylistProps<T>['footerActions']
sections: PlaylistProps<T>['sections']
onChangeSections?: (activeKeys: string[]) => void
onScrollListEdge: PlaylistProps<T>['onScrollListEdge']
loading: PlaylistProps<T>['loading']
emptyState: PlaylistProps<T>['listEmptyState']
controls?: JSX.Element | null
}): JSX.Element {
const [activeHeaderActionKey, setActiveHeaderActionKey] = useState<string | null>(null)
const lastScrollPositionRef = useRef(0)
const contentRef = useRef<HTMLDivElement | null>(null)

useEffect(() => {
if (contentRef.current) {
contentRef.current.scrollTop = 0
}
}, [activeHeaderActionKey])

const handleScroll = (e: React.UIEvent<HTMLDivElement>): void => {
// If we are scrolling down then check if we are at the bottom of the list
if (e.currentTarget.scrollTop > lastScrollPositionRef.current) {
Expand All @@ -202,62 +235,21 @@ function List<
}

const itemsCount = sections.flatMap((s) => s.items).length
const actionContent = headerActions?.find((a) => activeHeaderActionKey === a.key)?.content
const initiallyOpenSections = sections.filter((s) => s.initiallyOpen).map((s) => s.key)

return (
<div className="flex flex-col w-full bg-bg-light overflow-hidden border-r h-full">
<DraggableToNotebook href={notebooksHref}>
<div className="flex flex-col gap-1">
<div className="shrink-0 relative flex justify-between items-center p-1 gap-1 whitespace-nowrap border-b">
<LemonButton
size="small"
icon={<IconCollapse className="rotate-90" />}
onClick={onClickCollapse}
/>
<span className="py-1 flex flex-1 gap-2">
{title ? (
<span className="font-bold uppercase text-xs my-1 tracking-wide flex gap-1 items-center">
{title}
</span>
) : null}
<Tooltip
placement="bottom"
title={
<>
Showing {itemsCount} results.
<br />
Scrolling to the bottom or the top of the list will load older or newer results
respectively.
</>
}
>
<span>
<CounterBadge>{Math.min(999, itemsCount)}+</CounterBadge>
</span>
</Tooltip>
</span>
{headerActions.map(({ key, icon, tooltip, children }) => (
<LemonButton
key={key}
icon={icon}
tooltip={tooltip}
size="small"
active={activeHeaderActionKey === key}
onClick={() => setActiveHeaderActionKey(activeHeaderActionKey === key ? null : key)}
>
{children}
</LemonButton>
))}
<div className="shrink-0 relative flex justify-between items-center gap-0.5 whitespace-nowrap border-b">
<TitleWithCount title={title} count={itemsCount} onClickCollapse={onClickCollapse} />
{headerActions}
</div>
{controls ? <div className="w-full border-b">{controls}</div> : null}
<LemonTableLoader loading={loading} />
</div>
</DraggableToNotebook>

<div className="overflow-y-auto" onScroll={handleScroll} ref={contentRef}>
{actionContent && <div className="bg-bg-3000">{actionContent}</div>}

<div className="overflow-y-auto flex-1" onScroll={handleScroll} ref={contentRef}>
{sections.flatMap((s) => s.items).length ? (
<>
{sections.length > 1 ? (
Expand Down Expand Up @@ -286,6 +278,9 @@ function List<
emptyState
)}
</div>
<div className="shrink-0 relative flex justify-between items-center gap-0.5 whitespace-nowrap border-t">
{footerActions}
</div>
</div>
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,30 +10,39 @@ import { Tooltip } from 'lib/lemon-ui/Tooltip'
*/

interface SettingsMenuProps extends Omit<LemonMenuProps, 'items' | 'children'> {
label: string
label?: string
items: LemonMenuItem[]
icon?: JSX.Element
isAvailable?: boolean
whenUnavailable?: LemonMenuItem
highlightWhenActive?: boolean
closeOnClickInside?: boolean
}

export function SettingsMenu({
label,
items,
icon,
isAvailable = true,
closeOnClickInside = true,
highlightWhenActive = true,
whenUnavailable,
...props
}: SettingsMenuProps): JSX.Element {
const active = items.some((cf) => !!cf.active)
return (
<LemonMenu
buttonSize="xsmall"
closeOnClickInside={false}
closeOnClickInside={closeOnClickInside}
items={isAvailable ? items : whenUnavailable ? [whenUnavailable] : []}
{...props}
>
<LemonButton status={active ? 'danger' : 'default'} size="xsmall" icon={icon}>
<LemonButton
className="rounded-[0px]"
status={highlightWhenActive && active ? 'danger' : 'default'}
size="xsmall"
icon={icon}
>
{label}
</LemonButton>
</LemonMenu>
Expand All @@ -46,14 +55,20 @@ export function SettingsToggle({
label,
active,
...props
}: Omit<LemonButtonProps, 'status' | 'sideAction'> & {
}: Omit<LemonButtonProps, 'status' | 'sideAction' | 'className'> & {
active: boolean
title: string
icon?: JSX.Element | null
label: JSX.Element | string
}): JSX.Element {
const button = (
<LemonButton icon={icon} size="xsmall" status={active ? 'danger' : 'default'} {...props}>
<LemonButton
className="rounded-[0px]"
icon={icon}
size="xsmall"
status={active ? 'danger' : 'default'}
{...props}
>
{label}
</LemonButton>
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { Dayjs, dayjs } from 'lib/dayjs'
import { shortTimeZone } from 'lib/utils'

function formatStringFor(d: Dayjs): string {
const today = dayjs()
if (d.isSame(today, 'year')) {
return 'DD/MMM, HH:mm:ss'
}
return 'DD/MM/YYYY HH:mm:ss'
}

export function SimpleTimeLabel({ startTime, isUTC }: { startTime: string | number; isUTC: boolean }): JSX.Element {
let d = dayjs(startTime)
if (isUTC) {
d = d.tz('UTC')
}

return (
<div className="overflow-hidden text-ellipsis text-xs text-muted shrink-0">
{d.format(formatStringFor(d))} {isUTC ? 'UTC' : shortTimeZone(undefined, dayjs(d).toDate())}
</div>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@
flex-direction: row;
align-items: center;
justify-content: space-between;
height: 48px;

.PlayerMetaPersonProperties {
position: fixed;
Expand Down
7 changes: 1 addition & 6 deletions frontend/src/scenes/session-recordings/player/PlayerMeta.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -178,12 +178,7 @@ export function PlayerMeta({ iconsOnly }: { iconsOnly: boolean }): JSX.Element {
'PlayerMeta--fullscreen': isFullScreen,
})}
>
<div
className={clsx('flex items-center justify-between gap-2 whitespace-nowrap overflow-hidden', {
'p-2 h-10': !isFullScreen,
'p-1 px-3 text-xs h-12': isFullScreen,
})}
>
<div className="flex items-center justify-between gap-1 whitespace-nowrap overflow-hidden px-1 py-0.5 text-xs">
{sessionPlayerMetaDataLoading ? (
<LemonSkeleton className="w-1/3 h-4 my-1" />
) : (
Expand Down
Loading

0 comments on commit 7daba61

Please sign in to comment.