perfRow.tsx 5.0 KB

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