index.tsx 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  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 useExtractDomNodes from 'sentry/utils/replays/hooks/useExtractDomNodes';
  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} = useExtractDomNodes(
  31. {replay}
  32. );
  33. const startTimestampMs = replay?.getStartTimestampMs() ?? 0;
  34. const frames = replay?.getChapterFrames();
  35. const [scrollToRow, setScrollToRow] = useState<undefined | number>(undefined);
  36. const filterProps = useBreadcrumbFilters({frames: frames || []});
  37. const {expandPathsRef, items, searchTerm, setSearchTerm} = filterProps;
  38. const clearSearchTerm = () => setSearchTerm('');
  39. const listRef = useRef<ReactVirtualizedList>(null);
  40. const deps = useMemo(() => [items, searchTerm], [items, searchTerm]);
  41. const {cache, updateList} = useVirtualizedList({
  42. cellMeasurer,
  43. ref: listRef,
  44. deps,
  45. });
  46. const {handleDimensionChange: handleInspectorExpanded} = useVirtualizedInspector({
  47. cache,
  48. listRef,
  49. expandPathsRef,
  50. });
  51. const {
  52. handleClick: onClickToJump,
  53. onRowsRendered,
  54. showJumpDownButton,
  55. showJumpUpButton,
  56. } = useJumpButtons({
  57. currentTime,
  58. frames: items,
  59. isTable: false,
  60. setScrollToRow,
  61. });
  62. useScrollToCurrentItem({
  63. frames,
  64. ref: listRef,
  65. });
  66. // Need to refresh the item dimensions as DOM data gets loaded
  67. useEffect(() => {
  68. if (!isFetchingExtractions) {
  69. updateList();
  70. }
  71. }, [isFetchingExtractions, updateList]);
  72. const renderRow = ({index, key, style, parent}: ListRowProps) => {
  73. const item = (items || [])[index];
  74. return (
  75. <CellMeasurer
  76. cache={cache}
  77. columnIndex={0}
  78. key={key}
  79. parent={parent}
  80. rowIndex={index}
  81. >
  82. <BreadcrumbRow
  83. index={index}
  84. frame={item}
  85. extraction={frameToExtraction?.get(item)}
  86. startTimestampMs={startTimestampMs}
  87. style={style}
  88. expandPaths={Array.from(expandPathsRef.current?.get(index) || [])}
  89. onClick={() => {
  90. onClickTimestamp(item);
  91. }}
  92. onInspectorExpanded={handleInspectorExpanded}
  93. />
  94. </CellMeasurer>
  95. );
  96. };
  97. return (
  98. <FluidHeight>
  99. <FilterLoadingIndicator isLoading={isFetchingExtractions}>
  100. <BreadcrumbFilters frames={frames} {...filterProps} />
  101. </FilterLoadingIndicator>
  102. <TabItemContainer data-test-id="replay-details-breadcrumbs-tab">
  103. {frames ? (
  104. <AutoSizer onResize={updateList}>
  105. {({height, width}) => (
  106. <ReactVirtualizedList
  107. deferredMeasurementCache={cache}
  108. height={height}
  109. noRowsRenderer={() => (
  110. <NoRowRenderer
  111. unfilteredItems={frames}
  112. clearSearchTerm={clearSearchTerm}
  113. >
  114. {t('No breadcrumbs recorded')}
  115. </NoRowRenderer>
  116. )}
  117. onRowsRendered={onRowsRendered}
  118. onScroll={() => {
  119. if (scrollToRow !== undefined) {
  120. setScrollToRow(undefined);
  121. }
  122. }}
  123. overscanRowCount={5}
  124. ref={listRef}
  125. rowCount={items.length}
  126. rowHeight={cache.rowHeight}
  127. rowRenderer={renderRow}
  128. scrollToIndex={scrollToRow}
  129. width={width}
  130. />
  131. )}
  132. </AutoSizer>
  133. ) : (
  134. <Placeholder height="100%" />
  135. )}
  136. {items?.length ? (
  137. <JumpButtons
  138. jump={showJumpUpButton ? 'up' : showJumpDownButton ? 'down' : undefined}
  139. onClick={onClickToJump}
  140. tableHeaderHeight={0}
  141. />
  142. ) : null}
  143. </TabItemContainer>
  144. </FluidHeight>
  145. );
  146. }
  147. export default Breadcrumbs;