index.tsx 4.9 KB

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