import type {CSSProperties} from 'react'; import {Fragment, useCallback} from 'react'; import styled from '@emotion/styled'; import classNames from 'classnames'; import {Tooltip} from 'sentry/components/tooltip'; import {IconClock, IconRefresh} from 'sentry/icons'; import {tct} from 'sentry/locale'; import {space} from 'sentry/styles/space'; import getFrameDetails from 'sentry/utils/replays/getFrameDetails'; import useCrumbHandlers from 'sentry/utils/replays/hooks/useCrumbHandlers'; import IconWrapper from 'sentry/views/replays/detail/iconWrapper'; import TraceGrid from 'sentry/views/replays/detail/perfTable/traceGrid'; import type {ReplayTraceRow} from 'sentry/views/replays/detail/perfTable/useReplayPerfData'; import TimestampButton from 'sentry/views/replays/detail/timestampButton'; import type {OnDimensionChange} from 'sentry/views/replays/detail/useVirtualListDimentionChange'; interface Props { currentHoverTime: number | undefined; currentTime: number; index: number; onDimensionChange: OnDimensionChange; startTimestampMs: number; style: CSSProperties; traceRow: ReplayTraceRow; } export default function PerfRow({ currentHoverTime, currentTime, index, onDimensionChange, startTimestampMs, style, traceRow, }: Props) { const {lcpFrames, replayFrame: frame, paintFrames, flattenedTraces} = traceRow; const {color, description, title, icon} = getFrameDetails(frame); const lcp = lcpFrames.length ? getFrameDetails(lcpFrames[0]) : null; const handleDimensionChange = useCallback( () => onDimensionChange(index), [onDimensionChange, index] ); const {onMouseEnter, onMouseLeave, onClickTimestamp} = useCrumbHandlers(); const handleClickTimestamp = useCallback( () => onClickTimestamp(frame), [onClickTimestamp, frame] ); const handleMouseEnter = useCallback(() => onMouseEnter(frame), [onMouseEnter, frame]); const handleMouseLeave = useCallback(() => onMouseLeave(frame), [onMouseLeave, frame]); const hasOccurred = frame ? currentTime >= frame.offsetMs : false; const isBeforeHover = frame ? currentHoverTime === undefined || currentHoverTime >= frame.offsetMs : false; return ( {icon} {title} {description} {lcp ? ( {tct('[lcp] LCP', {lcp: lcp.description})} ) : null} {paintFrames.length ? ( {tct('[count] paint events', {count: paintFrames.length})} ) : null} {flattenedTraces.map((flatTrace, i) => ( ))} ); } const PerfListItem = styled('div')` padding: ${space(1)} ${space(1.5)}; /* Overridden in TabItemContainer, depending on *CurrentTime and *HoverTime classes */ border-top: 1px solid transparent; border-bottom: 1px solid transparent; `; const Vertical = styled('div')` display: flex; flex-direction: column; overflow-x: hidden; `; const Horizontal = styled('div')` display: flex; flex: auto 1 1; flex-direction: row; font-size: ${p => p.theme.fontSizeSmall}; overflow: auto; `; const Title = styled('span')<{hasOccurred?: boolean}>` color: ${p => (p.hasOccurred ? p.theme.gray400 : p.theme.gray300)}; font-size: ${p => p.theme.fontSizeMedium}; font-weight: bold; line-height: ${p => p.theme.text.lineHeightBody}; text-transform: capitalize; ${p => p.theme.overflowEllipsis}; `; const Description = styled(Tooltip)` ${p => p.theme.overflowEllipsis}; font-size: 0.7rem; font-variant-numeric: tabular-nums; line-height: ${p => p.theme.text.lineHeightBody}; color: ${p => p.theme.subText}; `; const IconLabel = styled('span')` display: flex; align-items: center; align-self: baseline; gap: 4px; `;