import {useRef} from 'react'; import styled from '@emotion/styled'; import EmptyMessage from 'sentry/components/emptyMessage'; import CompactSelect from 'sentry/components/forms/compactSelect'; import {Panel} from 'sentry/components/panels'; import {useReplayContext} from 'sentry/components/replays/replayContext'; import {relativeTimeInMs} from 'sentry/components/replays/utils'; import SearchBar from 'sentry/components/searchBar'; import {t} from 'sentry/locale'; import space from 'sentry/styles/space'; import type {BreadcrumbTypeDefault, Crumb} from 'sentry/types/breadcrumbs'; import {defined} from 'sentry/utils'; import {getPrevReplayEvent} from 'sentry/utils/replays/getReplayEvent'; import {useCurrentItemScroller} from 'sentry/utils/replays/hooks/useCurrentItemScroller'; import ConsoleMessage from 'sentry/views/replays/detail/console/consoleMessage'; import useConsoleFilters from 'sentry/views/replays/detail/console/useConsoleFilters'; import {getLogLevels} from 'sentry/views/replays/detail/console/utils'; import FluidHeight from 'sentry/views/replays/detail/layout/fluidHeight'; interface Props { breadcrumbs: Extract[]; startTimestampMs: number; } function Console({breadcrumbs, startTimestampMs = 0}: Props) { const {currentHoverTime, currentTime} = useReplayContext(); const containerRef = useRef(null); useCurrentItemScroller(containerRef); const {items, logLevel, searchTerm, setLogLevel, setSearchTerm} = useConsoleFilters({ breadcrumbs, }); const currentUserAction = getPrevReplayEvent({ items: breadcrumbs, targetTimestampMs: startTimestampMs + currentTime, allowExact: true, allowEqual: true, }); const closestUserAction = currentHoverTime !== undefined ? getPrevReplayEvent({ items: breadcrumbs, targetTimestampMs: startTimestampMs + (currentHoverTime ?? 0), allowExact: true, allowEqual: true, }) : undefined; const isOcurring = (breadcrumb: Crumb, closestBreadcrumb?: Crumb): boolean => { if (!defined(currentHoverTime) || !defined(closestBreadcrumb)) { return false; } const isCurrentBreadcrumb = closestBreadcrumb.id === breadcrumb.id; // We don't want to hightlight the breadcrumb if it's more than 1 second away from the current hover time const isMoreThanASecondOfDiff = Math.trunc(currentHoverTime / 1000) > Math.trunc( relativeTimeInMs(closestBreadcrumb.timestamp || '', startTimestampMs) / 1000 ); return isCurrentBreadcrumb && !isMoreThanASecondOfDiff; }; return ( ({ value, label: value, }))} onChange={selected => setLogLevel(selected.map(_ => _.value))} size="sm" value={logLevel} /> {items.length > 0 ? ( {items.map((breadcrumb, i) => { return ( = relativeTimeInMs(breadcrumb?.timestamp || '', startTimestampMs) } /> ); })} ) : ( )} ); } const ConsoleContainer = styled(FluidHeight)` height: 100%; `; const ConsoleFilters = styled('div')` display: grid; gap: ${space(1)}; grid-template-columns: max-content 1fr; margin-bottom: ${space(1)}; @media (max-width: ${p => p.theme.breakpoints.small}) { margin-top: ${space(1)}; } `; const ConsoleMessageContainer = styled(FluidHeight)` overflow: auto; border-radius: ${p => p.theme.borderRadius}; border: 1px solid ${p => p.theme.border}; box-shadow: ${p => p.theme.dropShadowLight}; `; const StyledEmptyMessage = styled(EmptyMessage)` align-items: center; `; const ConsoleTable = styled(Panel)` display: grid; grid-template-columns: max-content auto max-content; width: 100%; font-family: ${p => p.theme.text.familyMono}; font-size: 0.8em; border: none; box-shadow: none; margin-bottom: 0; `; export default Console;