index.tsx 6.1 KB

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