networkTableCell.tsx 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. import {CSSProperties, forwardRef} from 'react';
  2. import styled from '@emotion/styled';
  3. import FileSize from 'sentry/components/fileSize';
  4. import {useReplayContext} from 'sentry/components/replays/replayContext';
  5. import {relativeTimeInMs} from 'sentry/components/replays/utils';
  6. import {Tooltip} from 'sentry/components/tooltip';
  7. import space from 'sentry/styles/space';
  8. import useSortNetwork from 'sentry/views/replays/detail/network/useSortNetwork';
  9. import TimestampButton from 'sentry/views/replays/detail/timestampButton';
  10. import type {NetworkSpan} from 'sentry/views/replays/types';
  11. const EMPTY_CELL = '\u00A0';
  12. type Props = {
  13. columnIndex: number;
  14. handleClick: (span: NetworkSpan) => void;
  15. handleMouseEnter: (span: NetworkSpan) => void;
  16. handleMouseLeave: (span: NetworkSpan) => void;
  17. isCurrent: boolean;
  18. isHovered: boolean;
  19. sortConfig: ReturnType<typeof useSortNetwork>['sortConfig'];
  20. span: NetworkSpan;
  21. startTimestampMs: number;
  22. style: CSSProperties;
  23. };
  24. function NetworkTableCell({
  25. columnIndex,
  26. handleClick,
  27. handleMouseEnter,
  28. handleMouseLeave,
  29. isCurrent,
  30. isHovered,
  31. sortConfig,
  32. span,
  33. startTimestampMs,
  34. style,
  35. }: Props) {
  36. const {currentTime} = useReplayContext();
  37. const startMs = span.startTimestamp * 1000;
  38. const endMs = span.endTimestamp * 1000;
  39. const statusCode = span.data.statusCode;
  40. const isByTimestamp = sortConfig.by === 'startTimestamp';
  41. const columnProps = {
  42. hasOccurred: isByTimestamp
  43. ? currentTime >= relativeTimeInMs(span.startTimestamp * 1000, startTimestampMs)
  44. : undefined,
  45. hasOccurredAsc: isByTimestamp ? sortConfig.asc : undefined,
  46. isCurrent,
  47. isHovered,
  48. isStatusError: typeof statusCode === 'number' && statusCode >= 400,
  49. onMouseEnter: () => handleMouseEnter(span),
  50. onMouseLeave: () => handleMouseLeave(span),
  51. style,
  52. };
  53. const renderFns = [
  54. () => (
  55. <Cell {...columnProps}>
  56. <Text>{statusCode ? statusCode : EMPTY_CELL}</Text>
  57. </Cell>
  58. ),
  59. () => (
  60. <Cell {...columnProps}>
  61. <Tooltip
  62. title={span.description}
  63. isHoverable
  64. showOnlyOnOverflow
  65. overlayStyle={{maxWidth: '500px !important'}}
  66. >
  67. <Text>{span.description || EMPTY_CELL}</Text>
  68. </Tooltip>
  69. </Cell>
  70. ),
  71. () => (
  72. <Cell {...columnProps}>
  73. <Tooltip title={span.op.replace('resource.', '')} isHoverable showOnlyOnOverflow>
  74. <Text>{span.op.replace('resource.', '')}</Text>
  75. </Tooltip>
  76. </Cell>
  77. ),
  78. () => (
  79. <Cell {...columnProps} numeric>
  80. <Text>
  81. {span.data.size === undefined ? (
  82. EMPTY_CELL
  83. ) : (
  84. <FileSize bytes={span.data.size} />
  85. )}
  86. </Text>
  87. </Cell>
  88. ),
  89. () => (
  90. <Cell {...columnProps} numeric>
  91. <Text>{`${(endMs - startMs).toFixed(2)}ms`}</Text>
  92. </Cell>
  93. ),
  94. () => (
  95. <Cell {...columnProps} numeric>
  96. <TimestampButton
  97. format="mm:ss.SSS"
  98. onClick={() => handleClick(span)}
  99. startTimestampMs={startTimestampMs}
  100. timestampMs={startMs}
  101. />
  102. </Cell>
  103. ),
  104. ];
  105. return renderFns[columnIndex]();
  106. }
  107. const cellBackground = p => {
  108. if (p.hasOccurred === undefined && !p.isStatusError) {
  109. return `background-color: ${p.isHovered ? p.theme.hover : 'inherit'};`;
  110. }
  111. const color = p.isStatusError ? p.theme.alert.error.backgroundLight : 'inherit';
  112. return `background-color: ${color};`;
  113. };
  114. const cellBorder = p => {
  115. if (p.hasOccurred === undefined) {
  116. return null;
  117. }
  118. const color = p.isCurrent
  119. ? p.theme.purple300
  120. : p.isHovered
  121. ? p.theme.purple200
  122. : 'transparent';
  123. return p.hasOccurredAsc
  124. ? `border-bottom: 1px solid ${color};`
  125. : `border-top: 1px solid ${color};`;
  126. };
  127. const cellColor = p => {
  128. const colors = p.isStatusError
  129. ? [p.theme.alert.error.borderHover, p.theme.alert.error.iconColor]
  130. : ['inherit', p.theme.gray300];
  131. if (p.hasOccurred === undefined) {
  132. return `color: ${colors[0]};`;
  133. }
  134. return `color: ${p.hasOccurred ? colors[0] : colors[1]};`;
  135. };
  136. const Cell = styled('div')<{
  137. hasOccurred: boolean | undefined;
  138. hasOccurredAsc: boolean | undefined;
  139. isCurrent: boolean;
  140. isHovered: boolean;
  141. isStatusError: boolean;
  142. numeric?: boolean;
  143. }>`
  144. display: flex;
  145. align-items: center;
  146. padding: ${space(0.75)} ${space(1.5)};
  147. font-size: ${p => p.theme.fontSizeSmall};
  148. ${cellBackground}
  149. ${cellBorder}
  150. ${cellColor}
  151. ${p =>
  152. p.numeric &&
  153. `
  154. font-variant-numeric: tabular-nums;
  155. justify-content: flex-end;
  156. `};
  157. `;
  158. const Text = styled('div')`
  159. text-overflow: ellipsis;
  160. white-space: nowrap;
  161. overflow: hidden;
  162. `;
  163. export default forwardRef<HTMLDivElement, Props>(NetworkTableCell);