networkTableCell.tsx 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. import {ComponentProps, CSSProperties, forwardRef, MouseEvent, useMemo} from 'react';
  2. import classNames from 'classnames';
  3. import FileSize from 'sentry/components/fileSize';
  4. import {relativeTimeInMs} from 'sentry/components/replays/utils';
  5. import {
  6. Cell,
  7. StyledTimestampButton,
  8. Text,
  9. } from 'sentry/components/replays/virtualizedGrid/bodyCell';
  10. import {Tooltip} from 'sentry/components/tooltip';
  11. import useUrlParams from 'sentry/utils/useUrlParams';
  12. import useSortNetwork from 'sentry/views/replays/detail/network/useSortNetwork';
  13. import {operationName} from 'sentry/views/replays/detail/utils';
  14. import type {NetworkSpan} from 'sentry/views/replays/types';
  15. const EMPTY_CELL = '--';
  16. type Props = {
  17. columnIndex: number;
  18. currentHoverTime: number | undefined;
  19. currentTime: number;
  20. onClickCell: (props: {dataIndex: number; rowIndex: number}) => void;
  21. onClickTimestamp: (crumb: NetworkSpan) => void;
  22. onMouseEnter: (span: NetworkSpan) => void;
  23. onMouseLeave: (span: NetworkSpan) => void;
  24. rowIndex: number;
  25. sortConfig: ReturnType<typeof useSortNetwork>['sortConfig'];
  26. span: NetworkSpan;
  27. startTimestampMs: number;
  28. style: CSSProperties;
  29. };
  30. const NetworkTableCell = forwardRef<HTMLDivElement, Props>(
  31. (
  32. {
  33. columnIndex,
  34. currentHoverTime,
  35. currentTime,
  36. onMouseEnter,
  37. onMouseLeave,
  38. onClickCell,
  39. onClickTimestamp,
  40. rowIndex,
  41. sortConfig,
  42. span,
  43. startTimestampMs,
  44. style,
  45. }: Props,
  46. ref
  47. ) => {
  48. // Rows include the sortable header, the dataIndex does not
  49. const dataIndex = rowIndex - 1;
  50. const {getParamValue} = useUrlParams('n_detail_row', '');
  51. const isSelected = getParamValue() === String(dataIndex);
  52. const startMs = span.startTimestamp * 1000;
  53. const endMs = span.endTimestamp * 1000;
  54. const method = span.data.method;
  55. const statusCode = span.data.statusCode;
  56. // `data.responseBodySize` is from SDK version 7.44-7.45
  57. const size = span.data.size ?? span.data.response?.size ?? span.data.responseBodySize;
  58. const spanTime = useMemo(
  59. () => relativeTimeInMs(span.startTimestamp * 1000, startTimestampMs),
  60. [span.startTimestamp, startTimestampMs]
  61. );
  62. const hasOccurred = currentTime >= spanTime;
  63. const isBeforeHover = currentHoverTime === undefined || currentHoverTime >= spanTime;
  64. const isByTimestamp = sortConfig.by === 'startTimestamp';
  65. const isAsc = isByTimestamp ? sortConfig.asc : undefined;
  66. const columnProps = {
  67. className: classNames({
  68. beforeCurrentTime: isByTimestamp
  69. ? isAsc
  70. ? hasOccurred
  71. : !hasOccurred
  72. : undefined,
  73. afterCurrentTime: isByTimestamp
  74. ? isAsc
  75. ? !hasOccurred
  76. : hasOccurred
  77. : undefined,
  78. beforeHoverTime:
  79. isByTimestamp && currentHoverTime !== undefined
  80. ? isAsc
  81. ? isBeforeHover
  82. : !isBeforeHover
  83. : undefined,
  84. afterHoverTime:
  85. isByTimestamp && currentHoverTime !== undefined
  86. ? isAsc
  87. ? !isBeforeHover
  88. : isBeforeHover
  89. : undefined,
  90. }),
  91. hasOccurred: isByTimestamp ? hasOccurred : undefined,
  92. isSelected,
  93. isStatusError: typeof statusCode === 'number' && statusCode >= 400,
  94. onClick: () => onClickCell({dataIndex, rowIndex}),
  95. onMouseEnter: () => onMouseEnter(span),
  96. onMouseLeave: () => onMouseLeave(span),
  97. ref,
  98. style,
  99. } as ComponentProps<typeof Cell>;
  100. const renderFns = [
  101. () => (
  102. <Cell {...columnProps}>
  103. <Text>{method ? method : 'GET'}</Text>
  104. </Cell>
  105. ),
  106. () => (
  107. <Cell {...columnProps}>
  108. <Text>{typeof statusCode === 'number' ? statusCode : EMPTY_CELL}</Text>
  109. </Cell>
  110. ),
  111. () => (
  112. <Cell {...columnProps}>
  113. <Tooltip
  114. title={span.description}
  115. isHoverable
  116. showOnlyOnOverflow
  117. overlayStyle={{maxWidth: '500px !important'}}
  118. >
  119. <Text>{span.description || EMPTY_CELL}</Text>
  120. </Tooltip>
  121. </Cell>
  122. ),
  123. () => (
  124. <Cell {...columnProps}>
  125. <Tooltip title={operationName(span.op)} isHoverable showOnlyOnOverflow>
  126. <Text>{operationName(span.op)}</Text>
  127. </Tooltip>
  128. </Cell>
  129. ),
  130. () => (
  131. <Cell {...columnProps} numeric>
  132. <Text>
  133. {size === undefined ? EMPTY_CELL : <FileSize base={10} bytes={size} />}
  134. </Text>
  135. </Cell>
  136. ),
  137. () => (
  138. <Cell {...columnProps} numeric>
  139. <Text>{`${(endMs - startMs).toFixed(2)}ms`}</Text>
  140. </Cell>
  141. ),
  142. () => (
  143. <Cell {...columnProps} numeric>
  144. <StyledTimestampButton
  145. format="mm:ss.SSS"
  146. onClick={(event: MouseEvent) => {
  147. event.stopPropagation();
  148. onClickTimestamp(span);
  149. }}
  150. startTimestampMs={startTimestampMs}
  151. timestampMs={startMs}
  152. />
  153. </Cell>
  154. ),
  155. ];
  156. return renderFns[columnIndex]();
  157. }
  158. );
  159. export default NetworkTableCell;