index.tsx 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. import {useRef} from 'react';
  2. import {AutoSizer, CellMeasurer, GridCellProps, MultiGrid} from 'react-virtualized';
  3. import styled from '@emotion/styled';
  4. import Placeholder from 'sentry/components/placeholder';
  5. import {useReplayContext} from 'sentry/components/replays/replayContext';
  6. import {t} from 'sentry/locale';
  7. import {getPrevReplayEvent} from 'sentry/utils/replays/getReplayEvent';
  8. import useCrumbHandlers from 'sentry/utils/replays/hooks/useCrumbHandlers';
  9. import FluidHeight from 'sentry/views/replays/detail/layout/fluidHeight';
  10. import NetworkFilters from 'sentry/views/replays/detail/network/networkFilters';
  11. import NetworkHeaderCell, {
  12. COLUMN_COUNT,
  13. } from 'sentry/views/replays/detail/network/networkHeaderCell';
  14. import NetworkTableCell from 'sentry/views/replays/detail/network/networkTableCell';
  15. import useNetworkFilters from 'sentry/views/replays/detail/network/useNetworkFilters';
  16. import useSortNetwork from 'sentry/views/replays/detail/network/useSortNetwork';
  17. import NoRowRenderer from 'sentry/views/replays/detail/noRowRenderer';
  18. import useVirtualizedGrid from 'sentry/views/replays/detail/useVirtualizedGrid';
  19. import type {NetworkSpan} from 'sentry/views/replays/types';
  20. const HEADER_HEIGHT = 25;
  21. const BODY_HEIGHT = 28;
  22. type Props = {
  23. networkSpans: undefined | NetworkSpan[];
  24. startTimestampMs: number;
  25. };
  26. function NetworkList({networkSpans, startTimestampMs}: Props) {
  27. const {currentTime, currentHoverTime} = useReplayContext();
  28. const filterProps = useNetworkFilters({networkSpans: networkSpans || []});
  29. const {items: filteredItems, searchTerm, setSearchTerm} = filterProps;
  30. const clearSearchTerm = () => setSearchTerm('');
  31. const {handleSort, items, sortConfig} = useSortNetwork({items: filteredItems});
  32. const {handleMouseEnter, handleMouseLeave, handleClick} =
  33. useCrumbHandlers(startTimestampMs);
  34. const current = getPrevReplayEvent({
  35. items,
  36. targetTimestampMs: startTimestampMs + currentTime,
  37. allowEqual: true,
  38. allowExact: true,
  39. });
  40. const hovered = currentHoverTime
  41. ? getPrevReplayEvent({
  42. items,
  43. targetTimestampMs: startTimestampMs + currentHoverTime,
  44. allowEqual: true,
  45. allowExact: true,
  46. })
  47. : null;
  48. const gridRef = useRef<MultiGrid>(null);
  49. const {cache, getColumnWidth, onScrollbarPresenceChange, onWrapperResize} =
  50. useVirtualizedGrid({
  51. cellMeasurer: {
  52. defaultHeight: BODY_HEIGHT,
  53. defaultWidth: 100,
  54. fixedHeight: true,
  55. },
  56. gridRef,
  57. columnCount: COLUMN_COUNT,
  58. dynamicColumnIndex: 1,
  59. deps: [items, searchTerm],
  60. });
  61. const cellRenderer = ({columnIndex, rowIndex, key, style, parent}: GridCellProps) => {
  62. const network = items[rowIndex - 1];
  63. return (
  64. <CellMeasurer
  65. cache={cache}
  66. columnIndex={columnIndex}
  67. key={key}
  68. parent={parent}
  69. rowIndex={rowIndex}
  70. >
  71. {({
  72. measure: _,
  73. registerChild,
  74. }: {
  75. measure: () => void;
  76. registerChild?: (element?: Element) => void;
  77. }) =>
  78. rowIndex === 0 ? (
  79. <NetworkHeaderCell
  80. ref={e => e && registerChild?.(e)}
  81. handleSort={handleSort}
  82. index={columnIndex}
  83. sortConfig={sortConfig}
  84. style={{...style, height: HEADER_HEIGHT}}
  85. />
  86. ) : (
  87. <NetworkTableCell
  88. ref={e => e && registerChild?.(e)}
  89. columnIndex={columnIndex}
  90. handleClick={handleClick}
  91. handleMouseEnter={handleMouseEnter}
  92. handleMouseLeave={handleMouseLeave}
  93. isCurrent={network.id === current?.id}
  94. isHovered={network.id === hovered?.id}
  95. sortConfig={sortConfig}
  96. span={network}
  97. startTimestampMs={startTimestampMs}
  98. style={{...style, height: BODY_HEIGHT}}
  99. />
  100. )
  101. }
  102. </CellMeasurer>
  103. );
  104. };
  105. return (
  106. <NetworkContainer>
  107. <NetworkFilters networkSpans={networkSpans} {...filterProps} />
  108. <NetworkTable>
  109. {networkSpans ? (
  110. <AutoSizer onResize={onWrapperResize}>
  111. {({width, height}) => (
  112. <MultiGrid
  113. ref={gridRef}
  114. cellRenderer={cellRenderer}
  115. columnCount={COLUMN_COUNT}
  116. columnWidth={getColumnWidth(width)}
  117. estimatedColumnSize={width}
  118. estimatedRowSize={HEADER_HEIGHT + items.length * BODY_HEIGHT}
  119. fixedRowCount={1}
  120. height={height}
  121. noContentRenderer={() => (
  122. <NoRowRenderer
  123. unfilteredItems={networkSpans}
  124. clearSearchTerm={clearSearchTerm}
  125. >
  126. {t('No network requests recorded')}
  127. </NoRowRenderer>
  128. )}
  129. onScrollbarPresenceChange={onScrollbarPresenceChange}
  130. overscanColumnCount={COLUMN_COUNT}
  131. overscanRowCount={5}
  132. rowCount={items.length + 1}
  133. rowHeight={({index}) => (index === 0 ? HEADER_HEIGHT : BODY_HEIGHT)}
  134. width={width}
  135. />
  136. )}
  137. </AutoSizer>
  138. ) : (
  139. <Placeholder height="100%" />
  140. )}
  141. </NetworkTable>
  142. </NetworkContainer>
  143. );
  144. }
  145. const NetworkContainer = styled(FluidHeight)`
  146. height: 100%;
  147. `;
  148. const NetworkTable = styled('div')`
  149. position: relative;
  150. height: 100%;
  151. overflow: hidden;
  152. border: 1px solid ${p => p.theme.border};
  153. border-radius: ${p => p.theme.borderRadius};
  154. `;
  155. export default NetworkList;