index.tsx 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116
  1. import {memo, useMemo, useRef} from 'react';
  2. import {
  3. AutoSizer,
  4. CellMeasurer,
  5. List as ReactVirtualizedList,
  6. ListRowProps,
  7. } from 'react-virtualized';
  8. import {useQuery} from '@tanstack/react-query';
  9. import Placeholder from 'sentry/components/placeholder';
  10. import {useReplayContext} from 'sentry/components/replays/replayContext';
  11. import {t} from 'sentry/locale';
  12. import type ReplayReader from 'sentry/utils/replays/replayReader';
  13. import DomFilters from 'sentry/views/replays/detail/domMutations/domFilters';
  14. import DomMutationRow from 'sentry/views/replays/detail/domMutations/domMutationRow';
  15. import useDomFilters from 'sentry/views/replays/detail/domMutations/useDomFilters';
  16. import FluidHeight from 'sentry/views/replays/detail/layout/fluidHeight';
  17. import NoRowRenderer from 'sentry/views/replays/detail/noRowRenderer';
  18. import TabItemContainer from 'sentry/views/replays/detail/tabItemContainer';
  19. import useVirtualizedList from 'sentry/views/replays/detail/useVirtualizedList';
  20. type Props = {
  21. replay: null | ReplayReader;
  22. startTimestampMs: number;
  23. };
  24. // Ensure this object is created once as it is an input to
  25. // `useVirtualizedList`'s memoization
  26. const cellMeasurer = {
  27. fixedWidth: true,
  28. minHeight: 82,
  29. };
  30. function useExtractedDomNodes({replay}: {replay: null | ReplayReader}) {
  31. return useQuery(['getDomNodes', replay], () => replay?.getDomNodes() ?? [], {
  32. enabled: Boolean(replay),
  33. initialData: [],
  34. cacheTime: Infinity,
  35. });
  36. }
  37. function DomMutations({replay, startTimestampMs}: Props) {
  38. const {data: actions, isLoading} = useExtractedDomNodes({replay});
  39. const {currentTime, currentHoverTime} = useReplayContext();
  40. const filterProps = useDomFilters({actions: actions || []});
  41. const {items, setSearchTerm} = filterProps;
  42. const clearSearchTerm = () => setSearchTerm('');
  43. const listRef = useRef<ReactVirtualizedList>(null);
  44. const deps = useMemo(() => [items], [items]);
  45. const {cache, updateList} = useVirtualizedList({
  46. cellMeasurer,
  47. ref: listRef,
  48. deps,
  49. });
  50. const renderRow = ({index, key, style, parent}: ListRowProps) => {
  51. const mutation = items[index];
  52. return (
  53. <CellMeasurer
  54. cache={cache}
  55. columnIndex={0}
  56. key={key}
  57. parent={parent}
  58. rowIndex={index}
  59. >
  60. <DomMutationRow
  61. currentTime={currentTime}
  62. currentHoverTime={currentHoverTime}
  63. mutation={mutation}
  64. startTimestampMs={startTimestampMs}
  65. style={style}
  66. />
  67. </CellMeasurer>
  68. );
  69. };
  70. return (
  71. <FluidHeight>
  72. <DomFilters actions={actions} {...filterProps} />
  73. <TabItemContainer data-test-id="replay-details-dom-events-tab">
  74. {isLoading || !actions ? (
  75. <Placeholder height="100%" />
  76. ) : (
  77. <AutoSizer onResize={updateList}>
  78. {({width, height}) => (
  79. <ReactVirtualizedList
  80. deferredMeasurementCache={cache}
  81. height={height}
  82. noRowsRenderer={() => (
  83. <NoRowRenderer
  84. unfilteredItems={actions}
  85. clearSearchTerm={clearSearchTerm}
  86. >
  87. {t('No DOM events recorded')}
  88. </NoRowRenderer>
  89. )}
  90. overscanRowCount={5}
  91. ref={listRef}
  92. rowCount={items.length}
  93. rowHeight={cache.rowHeight}
  94. rowRenderer={renderRow}
  95. width={width}
  96. />
  97. )}
  98. </AutoSizer>
  99. )}
  100. </TabItemContainer>
  101. </FluidHeight>
  102. );
  103. }
  104. export default memo(DomMutations);