mirror of
https://github.com/hanzoai/sentinel.git
synced 2026-10-30 14:49:14 +00:00
163 lines
5.0 KiB
TypeScript
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;
|
|
`;
|