perfRow.tsx 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. import type {CSSProperties} from 'react';
  2. import {Fragment, useCallback} from 'react';
  3. import styled from '@emotion/styled';
  4. import classNames from 'classnames';
  5. import {Tooltip} from 'sentry/components/tooltip';
  6. import {IconClock, IconRefresh} from 'sentry/icons';
  7. import {tct} from 'sentry/locale';
  8. import {space} from 'sentry/styles/space';
  9. import getFrameDetails from 'sentry/utils/replays/getFrameDetails';
  10. import useCrumbHandlers from 'sentry/utils/replays/hooks/useCrumbHandlers';
  11. import IconWrapper from 'sentry/views/replays/detail/iconWrapper';
  12. import TraceGrid from 'sentry/views/replays/detail/perfTable/traceGrid';
  13. import type {ReplayTraceRow} from 'sentry/views/replays/detail/perfTable/useReplayPerfData';
  14. import TimestampButton from 'sentry/views/replays/detail/timestampButton';
  15. import type {OnDimensionChange} from 'sentry/views/replays/detail/useVirtualListDimentionChange';
  16. interface Props {
  17. currentHoverTime: number | undefined;
  18. currentTime: number;
  19. index: number;
  20. onDimensionChange: OnDimensionChange;
  21. startTimestampMs: number;
  22. style: CSSProperties;
  23. traceRow: ReplayTraceRow;
  24. }
  25. export default function PerfRow({
  26. currentHoverTime,
  27. currentTime,
  28. index,
  29. onDimensionChange,
  30. startTimestampMs,
  31. style,
  32. traceRow,
  33. }: Props) {
  34. const {lcpFrames, replayFrame: frame, paintFrames, flattenedTraces} = traceRow;
  35. const {color, description, title, icon} = getFrameDetails(frame);
  36. const lcp = lcpFrames.length ? getFrameDetails(lcpFrames[0]) : null;
  37. const handleDimensionChange = useCallback(
  38. () => onDimensionChange(index),
  39. [onDimensionChange, index]
  40. );
  41. const {onMouseEnter, onMouseLeave, onClickTimestamp} = useCrumbHandlers();
  42. const handleClickTimestamp = useCallback(
  43. () => onClickTimestamp(frame),
  44. [onClickTimestamp, frame]
  45. );
  46. const handleMouseEnter = useCallback(() => onMouseEnter(frame), [onMouseEnter, frame]);
  47. const handleMouseLeave = useCallback(() => onMouseLeave(frame), [onMouseLeave, frame]);
  48. const hasOccurred = frame ? currentTime >= frame.offsetMs : false;
  49. const isBeforeHover = frame
  50. ? currentHoverTime === undefined || currentHoverTime >= frame.offsetMs
  51. : false;
  52. return (
  53. <PerfListItem
  54. className={classNames({
  55. beforeCurrentTime: hasOccurred,
  56. afterCurrentTime: !hasOccurred,
  57. beforeHoverTime: currentHoverTime !== undefined && isBeforeHover,
  58. afterHoverTime: currentHoverTime !== undefined && !isBeforeHover,
  59. })}
  60. onMouseEnter={handleMouseEnter}
  61. onMouseLeave={handleMouseLeave}
  62. style={style}
  63. >
  64. <Vertical style={{gap: space(1)}}>
  65. <Horizontal style={{gap: space(1)}}>
  66. <IconWrapper color={color} hasOccurred={hasOccurred}>
  67. {icon}
  68. </IconWrapper>
  69. <Vertical style={{flexGrow: 1}}>
  70. <Title hasOccurred={hasOccurred}>{title}</Title>
  71. <Description title={description} showOnlyOnOverflow>
  72. {description}
  73. </Description>
  74. </Vertical>
  75. <IconLabel>
  76. {lcp ? (
  77. <Fragment>
  78. <IconClock size="xs" />
  79. {tct('[lcp] LCP', {lcp: lcp.description})}
  80. </Fragment>
  81. ) : null}
  82. </IconLabel>
  83. <IconLabel>
  84. {paintFrames.length ? (
  85. <Fragment>
  86. <IconRefresh size="xs" />
  87. {tct('[count] paint events', {count: paintFrames.length})}
  88. </Fragment>
  89. ) : null}
  90. </IconLabel>
  91. <TimestampButton
  92. onClick={handleClickTimestamp}
  93. startTimestampMs={startTimestampMs}
  94. timestampMs={frame.timestampMs}
  95. />
  96. </Horizontal>
  97. {flattenedTraces.map((flatTrace, i) => (
  98. <TraceGrid
  99. key={i}
  100. flattenedTrace={flatTrace}
  101. onDimensionChange={handleDimensionChange}
  102. />
  103. ))}
  104. </Vertical>
  105. </PerfListItem>
  106. );
  107. }
  108. const PerfListItem = styled('div')`
  109. padding: ${space(1)} ${space(1.5)};
  110. /* Overridden in TabItemContainer, depending on *CurrentTime and *HoverTime classes */
  111. border-top: 1px solid transparent;
  112. border-bottom: 1px solid transparent;
  113. `;
  114. const Vertical = styled('div')`
  115. display: flex;
  116. flex-direction: column;
  117. overflow-x: hidden;
  118. `;
  119. const Horizontal = styled('div')`
  120. display: flex;
  121. flex: auto 1 1;
  122. flex-direction: row;
  123. font-size: ${p => p.theme.fontSizeSmall};
  124. overflow: auto;
  125. `;
  126. const Title = styled('span')<{hasOccurred?: boolean}>`
  127. color: ${p => (p.hasOccurred ? p.theme.gray400 : p.theme.gray300)};
  128. font-size: ${p => p.theme.fontSizeMedium};
  129. font-weight: bold;
  130. line-height: ${p => p.theme.text.lineHeightBody};
  131. text-transform: capitalize;
  132. ${p => p.theme.overflowEllipsis};
  133. `;
  134. const Description = styled(Tooltip)`
  135. ${p => p.theme.overflowEllipsis};
  136. font-size: 0.7rem;
  137. font-variant-numeric: tabular-nums;
  138. line-height: ${p => p.theme.text.lineHeightBody};
  139. color: ${p => p.theme.subText};
  140. `;
  141. const IconLabel = styled('span')`
  142. display: flex;
  143. align-items: center;
  144. align-self: baseline;
  145. gap: 4px;
  146. `;