index.tsx 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  1. import {useEffect, useMemo, useRef, useState} from 'react';
  2. import type {ListRowProps} from 'react-virtualized';
  3. import {AutoSizer, CellMeasurer, List as ReactVirtualizedList} from 'react-virtualized';
  4. import Placeholder from 'sentry/components/placeholder';
  5. import JumpButtons from 'sentry/components/replays/jumpButtons';
  6. import {useReplayContext} from 'sentry/components/replays/replayContext';
  7. import useJumpButtons from 'sentry/components/replays/useJumpButtons';
  8. import {t} from 'sentry/locale';
  9. import useCrumbHandlers from 'sentry/utils/replays/hooks/useCrumbHandlers';
  10. import useExtractedDomNodes from 'sentry/utils/replays/hooks/useExtractedDomNodes';
  11. import useVirtualizedInspector from 'sentry/views/replays/detail//useVirtualizedInspector';
  12. import BreadcrumbFilters from 'sentry/views/replays/detail/breadcrumbs/breadcrumbFilters';
  13. import BreadcrumbRow from 'sentry/views/replays/detail/breadcrumbs/breadcrumbRow';
  14. import useBreadcrumbFilters from 'sentry/views/replays/detail/breadcrumbs/useBreadcrumbFilters';
  15. import useScrollToCurrentItem from 'sentry/views/replays/detail/breadcrumbs/useScrollToCurrentItem';
  16. import FilterLoadingIndicator from 'sentry/views/replays/detail/filterLoadingIndicator';
  17. import FluidHeight from 'sentry/views/replays/detail/layout/fluidHeight';
  18. import NoRowRenderer from 'sentry/views/replays/detail/noRowRenderer';
  19. import TabItemContainer from 'sentry/views/replays/detail/tabItemContainer';
  20. import useVirtualizedList from 'sentry/views/replays/detail/useVirtualizedList';
  21. // Ensure this object is created once as it is an input to
  22. // `useVirtualizedList`'s memoization
  23. const cellMeasurer = {
  24. fixedWidth: true,
  25. minHeight: 53,
  26. };
  27. function Breadcrumbs() {
  28. const {currentTime, replay} = useReplayContext();
  29. const {onClickTimestamp} = useCrumbHandlers();
  30. const {data: frameToExtraction, isFetching: isFetchingExtractions} =
  31. useExtractedDomNodes({replay});
  32. const startTimestampMs = replay?.getStartTimestampMs() ?? 0;
  33. const frames = replay?.getChapterFrames();
  34. const [scrollToRow, setScrollToRow] = useState<undefined | number>(undefined);
  35. const filterProps = useBreadcrumbFilters({frames: frames || []});
  36. const {expandPathsRef, items, searchTerm, setSearchTerm} = filterProps;
  37. const clearSearchTerm = () => setSearchTerm('');
  38. const listRef = useRef<ReactVirtualizedList>(null);
  39. const deps = useMemo(() => [items, searchTerm], [items, searchTerm]);
  40. const {cache, updateList} = useVirtualizedList({
  41. cellMeasurer,
  42. ref: listRef,
  43. deps,
  44. });
  45. const {handleDimensionChange: handleInspectorExpanded} = useVirtualizedInspector({
  46. cache,
  47. listRef,
  48. expandPathsRef,
  49. });
  50. const {
  51. handleClick: onClickToJump,
  52. onRowsRendered,
  53. showJumpDownButton,
  54. showJumpUpButton,
  55. } = useJumpButtons({
  56. currentTime,
  57. frames: items,
  58. isTable: false,
  59. setScrollToRow,
  60. });
  61. useScrollToCurrentItem({
  62. frames,
  63. ref: listRef,
  64. });
  65. // Need to refresh the item dimensions as DOM data gets loaded
  66. useEffect(() => {
  67. if (!isFetchingExtractions) {
  68. updateList();
  69. }
  70. }, [isFetchingExtractions, updateList]);
  71. const renderRow = ({index, key, style, parent}: ListRowProps) => {
  72. const item = (items || [])[index];
  73. return (
  74. <CellMeasurer
  75. cache={cache}
  76. columnIndex={0}
  77. key={key}
  78. parent={parent}
  79. rowIndex={index}
  80. >
  81. <BreadcrumbRow
  82. index={index}
  83. frame={item}
  84. extraction={frameToExtraction?.get(item)}
  85. startTimestampMs={startTimestampMs}
  86. style={style}
  87. expandPaths={Array.from(expandPathsRef.current?.get(index) || [])}
  88. onClick={() => {
  89. onClickTimestamp(item);
  90. }}
  91. onInspectorExpanded={handleInspectorExpanded}
  92. />
  93. </CellMeasurer>
  94. );
  95. };
  96. return (
  97. <FluidHeight>
  98. <FilterLoadingIndicator isLoading={isFetchingExtractions}>
  99. <BreadcrumbFilters frames={frames} {...filterProps} />
  100. </FilterLoadingIndicator>
  101. <TabItemContainer data-test-id="replay-details-breadcrumbs-tab">
  102. {frames ? (
  103. <AutoSizer onResize={updateList}>
  104. {({height, width}) => (
  105. <ReactVirtualizedList
  106. deferredMeasurementCache={cache}
  107. height={height}
  108. noRowsRenderer={() => (
  109. <NoRowRenderer
  110. unfilteredItems={frames}
  111. clearSearchTerm={clearSearchTerm}
  112. >
  113. {t('No breadcrumbs recorded')}
  114. </NoRowRenderer>
  115. )}
  116. onRowsRendered={onRowsRendered}
  117. onScroll={() => {
  118. if (scrollToRow !== undefined) {
  119. setScrollToRow(undefined);
  120. }
  121. }}
  122. overscanRowCount={5}
  123. ref={listRef}
  124. rowCount={items.length}
  125. rowHeight={cache.rowHeight}
  126. rowRenderer={renderRow}
  127. scrollToIndex={scrollToRow}
  128. width={width}
  129. />
  130. )}
  131. </AutoSizer>
  132. ) : (
  133. <Placeholder height="100%" />
  134. )}
  135. {items?.length ? (
  136. <JumpButtons
  137. jump={showJumpUpButton ? 'up' : showJumpDownButton ? 'down' : undefined}
  138. onClick={onClickToJump}
  139. tableHeaderHeight={0}
  140. />
  141. ) : null}
  142. </TabItemContainer>
  143. </FluidHeight>
  144. );
  145. }
  146. export default Breadcrumbs;