index.tsx 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  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 JumpButtons from 'sentry/components/replays/jumpButtons';
  11. import {useReplayContext} from 'sentry/components/replays/replayContext';
  12. import {t} from 'sentry/locale';
  13. import extractDomNodes from 'sentry/utils/replays/extractDomNodes';
  14. import useCrumbHandlers from 'sentry/utils/replays/hooks/useCrumbHandlers';
  15. import type ReplayReader from 'sentry/utils/replays/replayReader';
  16. import DomFilters from 'sentry/views/replays/detail/domMutations/domFilters';
  17. import DomMutationRow from 'sentry/views/replays/detail/domMutations/domMutationRow';
  18. import useDomFilters from 'sentry/views/replays/detail/domMutations/useDomFilters';
  19. import FilterLoadingIndicator from 'sentry/views/replays/detail/filterLoadingIndicator';
  20. import FluidHeight from 'sentry/views/replays/detail/layout/fluidHeight';
  21. import NoRowRenderer from 'sentry/views/replays/detail/noRowRenderer';
  22. import TabItemContainer from 'sentry/views/replays/detail/tabItemContainer';
  23. import useVirtualizedList from 'sentry/views/replays/detail/useVirtualizedList';
  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(
  32. ['getDomNodes', replay],
  33. () =>
  34. extractDomNodes({
  35. frames: replay?.getDOMFrames(),
  36. rrwebEvents: replay?.getRRWebFrames(),
  37. startTimestampMs: replay?.getReplay().started_at.getTime() ?? 0,
  38. }),
  39. {enabled: Boolean(replay), cacheTime: Infinity}
  40. );
  41. }
  42. function DomMutations() {
  43. const {currentTime, currentHoverTime, replay} = useReplayContext();
  44. const {onMouseEnter, onMouseLeave, onClickTimestamp} = useCrumbHandlers();
  45. const {data: frameToExtraction, isFetching} = useExtractedDomNodes({replay});
  46. const actions = useMemo(
  47. () => Array.from(frameToExtraction?.values() || []),
  48. [frameToExtraction]
  49. );
  50. const startTimestampMs = replay?.getReplay()?.started_at?.getTime() ?? 0;
  51. const filterProps = useDomFilters({actions});
  52. const {items, setSearchTerm} = filterProps;
  53. const clearSearchTerm = () => setSearchTerm('');
  54. const listRef = useRef<ReactVirtualizedList>(null);
  55. const deps = useMemo(() => [items], [items]);
  56. const {cache, updateList} = useVirtualizedList({
  57. cellMeasurer,
  58. ref: listRef,
  59. deps,
  60. });
  61. const renderRow = ({index, key, style, parent}: ListRowProps) => {
  62. const mutation = items[index];
  63. return (
  64. <CellMeasurer
  65. cache={cache}
  66. columnIndex={0}
  67. key={key}
  68. parent={parent}
  69. rowIndex={index}
  70. >
  71. <DomMutationRow
  72. currentHoverTime={currentHoverTime}
  73. currentTime={currentTime}
  74. onMouseEnter={onMouseEnter}
  75. onMouseLeave={onMouseLeave}
  76. mutation={mutation}
  77. onClickTimestamp={onClickTimestamp}
  78. startTimestampMs={startTimestampMs}
  79. style={style}
  80. />
  81. </CellMeasurer>
  82. );
  83. };
  84. const showJumpUpButton = false;
  85. const showJumpDownButton = false;
  86. return (
  87. <FluidHeight>
  88. <FilterLoadingIndicator isLoading={isFetching}>
  89. <DomFilters actions={actions} {...filterProps} />
  90. </FilterLoadingIndicator>
  91. <TabItemContainer data-test-id="replay-details-dom-events-tab">
  92. {isFetching || !actions ? (
  93. <Placeholder height="100%" />
  94. ) : (
  95. <AutoSizer onResize={updateList}>
  96. {({width, height}) => (
  97. <ReactVirtualizedList
  98. deferredMeasurementCache={cache}
  99. height={height}
  100. noRowsRenderer={() => (
  101. <NoRowRenderer
  102. unfilteredItems={actions}
  103. clearSearchTerm={clearSearchTerm}
  104. >
  105. {t('No DOM events recorded')}
  106. </NoRowRenderer>
  107. )}
  108. overscanRowCount={5}
  109. ref={listRef}
  110. rowCount={items.length}
  111. rowHeight={cache.rowHeight}
  112. rowRenderer={renderRow}
  113. width={width}
  114. />
  115. )}
  116. </AutoSizer>
  117. )}
  118. <JumpButtons
  119. jump={showJumpUpButton ? 'up' : showJumpDownButton ? 'down' : undefined}
  120. onClick={() => {}}
  121. tableHeaderHeight={0}
  122. />
  123. </TabItemContainer>
  124. </FluidHeight>
  125. );
  126. }
  127. export default memo(DomMutations);