123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162 |
- 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;
- `;
|