index.tsx 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
  1. import {useMemo, useRef} 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 {t} from 'sentry/locale';
  10. import getFrameDetails from 'sentry/utils/replays/getFrameDetails';
  11. import useActiveReplayTab from 'sentry/utils/replays/hooks/useActiveReplayTab';
  12. import useCrumbHandlers from 'sentry/utils/replays/hooks/useCrumbHandlers';
  13. import type {ReplayFrame} from 'sentry/utils/replays/types';
  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 FluidHeight from 'sentry/views/replays/detail/layout/fluidHeight';
  19. import NoRowRenderer from 'sentry/views/replays/detail/noRowRenderer';
  20. import TabItemContainer from 'sentry/views/replays/detail/tabItemContainer';
  21. import useVirtualizedList from 'sentry/views/replays/detail/useVirtualizedList';
  22. import useVirtualizedInspector from '../useVirtualizedInspector';
  23. type Props = {
  24. frames: undefined | ReplayFrame[];
  25. startTimestampMs: number;
  26. };
  27. // Ensure this object is created once as it is an input to
  28. // `useVirtualizedList`'s memoization
  29. const cellMeasurer = {
  30. fixedWidth: true,
  31. minHeight: 53,
  32. };
  33. function Breadcrumbs({frames, startTimestampMs}: Props) {
  34. const {onClickTimestamp} = useCrumbHandlers();
  35. const {setActiveTab} = useActiveReplayTab();
  36. const listRef = useRef<ReactVirtualizedList>(null);
  37. // Keep a reference of object paths that are expanded (via <ObjectInspector>)
  38. // by log row, so they they can be restored as the Console pane is scrolling.
  39. // Due to virtualization, components can be unmounted as the user scrolls, so
  40. // state needs to be remembered.
  41. //
  42. // Note that this is intentionally not in state because we do not want to
  43. // re-render when items are expanded/collapsed, though it may work in state as well.
  44. const expandPathsRef = useRef(new Map<number, Set<string>>());
  45. const filterProps = useBreadcrumbFilters({frames: frames || []});
  46. const {items, searchTerm, setSearchTerm} = filterProps;
  47. const clearSearchTerm = () => setSearchTerm('');
  48. const deps = useMemo(() => [items, searchTerm], [items, searchTerm]);
  49. const {cache, updateList} = useVirtualizedList({
  50. cellMeasurer,
  51. ref: listRef,
  52. deps,
  53. });
  54. const {handleDimensionChange} = useVirtualizedInspector({
  55. cache,
  56. listRef,
  57. expandPathsRef,
  58. });
  59. useScrollToCurrentItem({
  60. frames,
  61. ref: listRef,
  62. });
  63. const renderRow = ({index, key, style, parent}: ListRowProps) => {
  64. const item = (items || [])[index];
  65. return (
  66. <CellMeasurer
  67. cache={cache}
  68. columnIndex={0}
  69. key={key}
  70. parent={parent}
  71. rowIndex={index}
  72. >
  73. <BreadcrumbRow
  74. index={index}
  75. frame={item}
  76. startTimestampMs={startTimestampMs}
  77. style={style}
  78. expandPaths={Array.from(expandPathsRef.current?.get(index) || [])}
  79. onClick={() => {
  80. onClickTimestamp(item);
  81. setActiveTab(getFrameDetails(item).tabKey);
  82. }}
  83. onDimensionChange={handleDimensionChange}
  84. />
  85. </CellMeasurer>
  86. );
  87. };
  88. return (
  89. <FluidHeight>
  90. <BreadcrumbFilters frames={frames} {...filterProps} />
  91. <TabItemContainer data-test-id="replay-details-breadcrumbs-tab">
  92. {frames ? (
  93. <AutoSizer onResize={updateList}>
  94. {({height, width}) => (
  95. <ReactVirtualizedList
  96. deferredMeasurementCache={cache}
  97. height={height}
  98. noRowsRenderer={() => (
  99. <NoRowRenderer
  100. unfilteredItems={frames}
  101. clearSearchTerm={clearSearchTerm}
  102. >
  103. {t('No breadcrumbs recorded')}
  104. </NoRowRenderer>
  105. )}
  106. overscanRowCount={5}
  107. ref={listRef}
  108. rowCount={items.length}
  109. rowHeight={cache.rowHeight}
  110. rowRenderer={renderRow}
  111. width={width}
  112. />
  113. )}
  114. </AutoSizer>
  115. ) : (
  116. <Placeholder height="100%" />
  117. )}
  118. </TabItemContainer>
  119. </FluidHeight>
  120. );
  121. }
  122. export default Breadcrumbs;