Files
2024-02-15 17:01:50 +00:00

163 lines
5.0 KiB
TypeScript

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 (
<PerfListItem
className={classNames({
beforeCurrentTime: hasOccurred,
afterCurrentTime: !hasOccurred,
beforeHoverTime: currentHoverTime !== undefined && isBeforeHover,
afterHoverTime: currentHoverTime !== undefined && !isBeforeHover,
})}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
style={style}
>
<Vertical style={{gap: space(1)}}>
<Horizontal style={{gap: space(1)}}>
<IconWrapper color={color} hasOccurred={hasOccurred}>
{icon}
</IconWrapper>
<Vertical style={{flexGrow: 1}}>
<Title hasOccurred={hasOccurred}>{title}</Title>
<Description title={description} showOnlyOnOverflow>
{description}
</Description>
</Vertical>
<IconLabel>
{lcp ? (
<Fragment>
<IconClock size="xs" />
{tct('[lcp] LCP', {lcp: lcp.description})}
</Fragment>
) : null}
</IconLabel>
<IconLabel>
{paintFrames.length ? (
<Fragment>
<IconRefresh size="xs" />
{tct('[count] paint events', {count: paintFrames.length})}
</Fragment>
) : null}
</IconLabel>
<TimestampButton
onClick={handleClickTimestamp}
startTimestampMs={startTimestampMs}
timestampMs={frame.timestampMs}
/>
</Horizontal>
{flattenedTraces.map((flatTrace, i) => (
<TraceGrid
key={i}
flattenedTrace={flatTrace}
onDimensionChange={handleDimensionChange}
/>
))}
</Vertical>
</PerfListItem>
);
}
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;
`;