networkTableCell.tsx 5.3 KB

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