networkTableCell.tsx 5.3 KB

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