import {useCallback, useEffect, useMemo, useRef, useState} from 'react'; import type {GridCellProps} from 'react-virtualized'; import {AutoSizer, CellMeasurer, MultiGrid} from 'react-virtualized'; import styled from '@emotion/styled'; import Placeholder from 'sentry/components/placeholder'; import JumpButtons from 'sentry/components/replays/jumpButtons'; import {useReplayContext} from 'sentry/components/replays/replayContext'; import useJumpButtons from 'sentry/components/replays/useJumpButtons'; import {GridTable} from 'sentry/components/replays/virtualizedGrid/gridTable'; import {OverflowHidden} from 'sentry/components/replays/virtualizedGrid/overflowHidden'; import {SplitPanel} from 'sentry/components/replays/virtualizedGrid/splitPanel'; import useDetailsSplit from 'sentry/components/replays/virtualizedGrid/useDetailsSplit'; import {t} from 'sentry/locale'; import {trackAnalytics} from 'sentry/utils/analytics'; import useA11yData from 'sentry/utils/replays/hooks/useA11yData'; import useCrumbHandlers from 'sentry/utils/replays/hooks/useCrumbHandlers'; import useCurrentHoverTime from 'sentry/utils/replays/playback/providers/useCurrentHoverTime'; import useOrganization from 'sentry/utils/useOrganization'; import AccessibilityFilters from 'sentry/views/replays/detail/accessibility/accessibilityFilters'; import AccessibilityHeaderCell, { COLUMN_COUNT, } from 'sentry/views/replays/detail/accessibility/accessibilityHeaderCell'; import AccessibilityRefetchBanner from 'sentry/views/replays/detail/accessibility/accessibilityRefetchBanner'; import AccessibilityTableCell from 'sentry/views/replays/detail/accessibility/accessibilityTableCell'; import AccessibilityDetails from 'sentry/views/replays/detail/accessibility/details'; import useAccessibilityFilters from 'sentry/views/replays/detail/accessibility/useAccessibilityFilters'; import useSortAccessibility from 'sentry/views/replays/detail/accessibility/useSortAccessibility'; import FilterLoadingIndicator from 'sentry/views/replays/detail/filterLoadingIndicator'; import FluidHeight from 'sentry/views/replays/detail/layout/fluidHeight'; import NoRowRenderer from 'sentry/views/replays/detail/noRowRenderer'; import useVirtualizedGrid from 'sentry/views/replays/detail/useVirtualizedGrid'; const HEADER_HEIGHT = 25; const BODY_HEIGHT = 25; const RESIZEABLE_HANDLE_HEIGHT = 105; const cellMeasurer = { defaultHeight: BODY_HEIGHT, defaultWidth: 100, fixedHeight: true, }; function AccessibilityList() { const organization = useOrganization(); const {currentTime} = useReplayContext(); const [currentHoverTime] = useCurrentHoverTime(); const {onMouseEnter, onMouseLeave, onClickTimestamp} = useCrumbHandlers(); const { dataOffsetMs, data: accessibilityData, isLoading, isRefetching, refetch, } = useA11yData(); const [scrollToRow, setScrollToRow] = useState(undefined); const filterProps = useAccessibilityFilters({ accessibilityData: accessibilityData || [], }); const {items: filteredItems, searchTerm, setSearchTerm} = filterProps; const clearSearchTerm = () => setSearchTerm(''); const {handleSort, items, sortConfig} = useSortAccessibility({items: filteredItems}); const containerRef = useRef(null); const gridRef = useRef(null); const deps = useMemo(() => [items, searchTerm], [items, searchTerm]); const {cache, getColumnWidth, onScrollbarPresenceChange, onWrapperResize} = useVirtualizedGrid({ cellMeasurer, gridRef, columnCount: COLUMN_COUNT, dynamicColumnIndex: 2, deps, }); const { onClickCell, onCloseDetailsSplit, resizableDrawerProps, selectedIndex, splitSize, } = useDetailsSplit({ containerRef, handleHeight: RESIZEABLE_HANDLE_HEIGHT, frames: accessibilityData, urlParamName: 'a_detail_row', onShowDetails: useCallback( ({dataIndex, rowIndex}) => { setScrollToRow(rowIndex); const item = items[dataIndex]; trackAnalytics('replay.accessibility-issue-clicked', { organization, issue_description: item.description, issue_impact: item.impact, }); }, [items, organization] ), }); useEffect(() => { if (isRefetching) { onCloseDetailsSplit(); } }, [isRefetching, onCloseDetailsSplit]); const { handleClick: onClickToJump, onSectionRendered, showJumpDownButton, showJumpUpButton, } = useJumpButtons({ currentTime, frames: filteredItems, isTable: true, setScrollToRow, }); const cellRenderer = ({columnIndex, rowIndex, key, style, parent}: GridCellProps) => { const a11yIssue = items[rowIndex - 1]; return ( {({measure: _, registerChild}) => rowIndex === 0 ? ( e && registerChild?.(e)} handleSort={handleSort} index={columnIndex} sortConfig={sortConfig} style={{...style, height: HEADER_HEIGHT}} /> ) : ( e && registerChild?.(e)} rowIndex={rowIndex} sortConfig={sortConfig} style={{...style, height: BODY_HEIGHT}} /> ) } ); }; return ( {accessibilityData && !isRefetching ? ( {({height, width}) => ( ( {t('No accessibility problems detected')} )} onScrollbarPresenceChange={onScrollbarPresenceChange} onScroll={() => { if (scrollToRow !== undefined) { setScrollToRow(undefined); } }} onSectionRendered={onSectionRendered} overscanColumnCount={COLUMN_COUNT} overscanRowCount={5} rowCount={items.length + 1} rowHeight={({index}) => (index === 0 ? HEADER_HEIGHT : BODY_HEIGHT)} scrollToRow={scrollToRow} width={width} /> )} {sortConfig.by === 'timestamp' && items.length ? ( ) : null} ) : ( )} ); } const StyledGridTable = styled(GridTable)` border-radius: ${p => p.theme.borderRadiusBottom}; border-top: none; `; export default AccessibilityList;