index.tsx 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  1. import {memo, 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 useCurrentHoverTime from 'sentry/utils/replays/playback/providers/useCurrentHoverTime';
  11. import ConsoleFilters from 'sentry/views/replays/detail/console/consoleFilters';
  12. import ConsoleLogRow from 'sentry/views/replays/detail/console/consoleLogRow';
  13. import useConsoleFilters from 'sentry/views/replays/detail/console/useConsoleFilters';
  14. import FluidHeight from 'sentry/views/replays/detail/layout/fluidHeight';
  15. import NoRowRenderer from 'sentry/views/replays/detail/noRowRenderer';
  16. import TabItemContainer from 'sentry/views/replays/detail/tabItemContainer';
  17. import useVirtualizedList from 'sentry/views/replays/detail/useVirtualizedList';
  18. import useVirtualizedInspector from '../useVirtualizedInspector';
  19. // Ensure this object is created once as it is an input to
  20. // `useVirtualizedList`'s memoization
  21. const cellMeasurer = {
  22. fixedWidth: true,
  23. minHeight: 24,
  24. };
  25. function Console() {
  26. const {currentTime, replay} = useReplayContext();
  27. const [currentHoverTime] = useCurrentHoverTime();
  28. const {onMouseEnter, onMouseLeave, onClickTimestamp} = useCrumbHandlers();
  29. const startTimestampMs = replay?.getReplay()?.started_at?.getTime() ?? 0;
  30. const frames = replay?.getConsoleFrames();
  31. const [scrollToRow, setScrollToRow] = useState<undefined | number>(undefined);
  32. const filterProps = useConsoleFilters({frames: frames || []});
  33. const {expandPathsRef, searchTerm, logLevel, items, setSearchTerm} = filterProps;
  34. const clearSearchTerm = () => setSearchTerm('');
  35. const listRef = useRef<ReactVirtualizedList>(null);
  36. const deps = useMemo(() => [items], [items]);
  37. const {cache, updateList} = useVirtualizedList({
  38. cellMeasurer,
  39. ref: listRef,
  40. deps,
  41. });
  42. const {handleDimensionChange} = useVirtualizedInspector({
  43. cache,
  44. listRef,
  45. expandPathsRef,
  46. });
  47. const {
  48. handleClick: onClickToJump,
  49. onRowsRendered,
  50. showJumpDownButton,
  51. showJumpUpButton,
  52. } = useJumpButtons({
  53. currentTime,
  54. frames: items,
  55. isTable: false,
  56. setScrollToRow,
  57. });
  58. const renderRow = ({index, key, style, parent}: ListRowProps) => {
  59. const item = items[index];
  60. return (
  61. <CellMeasurer
  62. cache={cache}
  63. columnIndex={0}
  64. // Set key based on filters, otherwise we can have odd expand/collapse state
  65. // with <StructuredEventData> when filtering
  66. key={`${searchTerm}-${logLevel.join(',')}-${key}`}
  67. parent={parent}
  68. rowIndex={index}
  69. >
  70. <ConsoleLogRow
  71. currentHoverTime={currentHoverTime}
  72. currentTime={currentTime}
  73. expandPaths={Array.from(expandPathsRef.current?.get(index) || [])}
  74. frame={item}
  75. onMouseEnter={onMouseEnter}
  76. onMouseLeave={onMouseLeave}
  77. index={index}
  78. onClickTimestamp={onClickTimestamp}
  79. onDimensionChange={handleDimensionChange}
  80. startTimestampMs={startTimestampMs}
  81. style={style}
  82. />
  83. </CellMeasurer>
  84. );
  85. };
  86. return (
  87. <FluidHeight>
  88. <ConsoleFilters frames={frames} {...filterProps} />
  89. <TabItemContainer data-test-id="replay-details-console-tab">
  90. {frames ? (
  91. <AutoSizer onResize={updateList}>
  92. {({width, height}) => (
  93. <ReactVirtualizedList
  94. deferredMeasurementCache={cache}
  95. height={height}
  96. noRowsRenderer={() => (
  97. <NoRowRenderer
  98. unfilteredItems={frames}
  99. clearSearchTerm={clearSearchTerm}
  100. >
  101. {t('No console logs recorded')}
  102. </NoRowRenderer>
  103. )}
  104. onRowsRendered={onRowsRendered}
  105. onScroll={() => {
  106. if (scrollToRow !== undefined) {
  107. setScrollToRow(undefined);
  108. }
  109. }}
  110. overscanRowCount={5}
  111. ref={listRef}
  112. rowCount={items.length}
  113. rowHeight={cache.rowHeight}
  114. rowRenderer={renderRow}
  115. scrollToIndex={scrollToRow}
  116. width={width}
  117. />
  118. )}
  119. </AutoSizer>
  120. ) : (
  121. <Placeholder height="100%" />
  122. )}
  123. {items?.length ? (
  124. <JumpButtons
  125. jump={showJumpUpButton ? 'up' : showJumpDownButton ? 'down' : undefined}
  126. onClick={onClickToJump}
  127. tableHeaderHeight={0}
  128. />
  129. ) : null}
  130. </TabItemContainer>
  131. </FluidHeight>
  132. );
  133. }
  134. export default memo(Console);