index.tsx 6.4 KB

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