index.tsx 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  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 useProjectFromId from 'sentry/utils/useProjectFromId';
  13. import useVirtualizedInspector from 'sentry/views/replays/detail//useVirtualizedInspector';
  14. import BreadcrumbFilters from 'sentry/views/replays/detail/breadcrumbs/breadcrumbFilters';
  15. import BreadcrumbRow from 'sentry/views/replays/detail/breadcrumbs/breadcrumbRow';
  16. import useBreadcrumbFilters from 'sentry/views/replays/detail/breadcrumbs/useBreadcrumbFilters';
  17. import useScrollToCurrentItem from 'sentry/views/replays/detail/breadcrumbs/useScrollToCurrentItem';
  18. import FilterLoadingIndicator from 'sentry/views/replays/detail/filterLoadingIndicator';
  19. import FluidHeight from 'sentry/views/replays/detail/layout/fluidHeight';
  20. import NoRowRenderer from 'sentry/views/replays/detail/noRowRenderer';
  21. import useReplayPerfData from 'sentry/views/replays/detail/perfTable/useReplayPerfData';
  22. import TabItemContainer from 'sentry/views/replays/detail/tabItemContainer';
  23. import useVirtualizedList from 'sentry/views/replays/detail/useVirtualizedList';
  24. import useVirtualListDimentionChange from 'sentry/views/replays/detail/useVirtualListDimentionChange';
  25. // Ensure this object is created once as it is an input to
  26. // `useVirtualizedList`'s memoization
  27. const cellMeasurer = {
  28. fixedWidth: true,
  29. minHeight: 53,
  30. };
  31. function Breadcrumbs() {
  32. const {currentTime, replay} = useReplayContext();
  33. const organization = useOrganization();
  34. const hasPerfTab = organization.features.includes('session-replay-trace-table');
  35. const projectSlug = useProjectFromId({
  36. project_id: replay?.getReplay().project_id,
  37. })?.slug;
  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?.getStartTimestampMs() ?? 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. projectSlug={projectSlug}
  98. startTimestampMs={startTimestampMs}
  99. style={style}
  100. expandPaths={Array.from(expandPathsRef.current?.get(index) || [])}
  101. onClick={() => {
  102. onClickTimestamp(item);
  103. }}
  104. onDimensionChange={handleDimensionChange}
  105. onInspectorExpanded={handleInspectorExpanded}
  106. />
  107. </CellMeasurer>
  108. );
  109. };
  110. return (
  111. <FluidHeight>
  112. <FilterLoadingIndicator isLoading={isFetchingExtractions || isFetchingTraces}>
  113. <BreadcrumbFilters frames={frames} {...filterProps} />
  114. </FilterLoadingIndicator>
  115. <TabItemContainer data-test-id="replay-details-breadcrumbs-tab">
  116. {frames ? (
  117. <AutoSizer onResize={updateList}>
  118. {({height, width}) => (
  119. <ReactVirtualizedList
  120. deferredMeasurementCache={cache}
  121. height={height}
  122. noRowsRenderer={() => (
  123. <NoRowRenderer
  124. unfilteredItems={frames}
  125. clearSearchTerm={clearSearchTerm}
  126. >
  127. {t('No breadcrumbs recorded')}
  128. </NoRowRenderer>
  129. )}
  130. onRowsRendered={onRowsRendered}
  131. onScroll={() => {
  132. if (scrollToRow !== undefined) {
  133. setScrollToRow(undefined);
  134. }
  135. }}
  136. overscanRowCount={5}
  137. ref={listRef}
  138. rowCount={items.length}
  139. rowHeight={cache.rowHeight}
  140. rowRenderer={renderRow}
  141. scrollToIndex={scrollToRow}
  142. width={width}
  143. />
  144. )}
  145. </AutoSizer>
  146. ) : (
  147. <Placeholder height="100%" />
  148. )}
  149. {items?.length ? (
  150. <JumpButtons
  151. jump={showJumpUpButton ? 'up' : showJumpDownButton ? 'down' : undefined}
  152. onClick={onClickToJump}
  153. tableHeaderHeight={0}
  154. />
  155. ) : null}
  156. </TabItemContainer>
  157. </FluidHeight>
  158. );
  159. }
  160. export default Breadcrumbs;