import {Fragment, useMemo, useState} from 'react'; import styled from '@emotion/styled'; import debounce from 'lodash/debounce'; import CompactSelect from 'sentry/components/forms/compactSelect'; import {Panel} from 'sentry/components/panels'; import {useReplayContext} from 'sentry/components/replays/replayContext'; import {relativeTimeInMs, showPlayerTime} from 'sentry/components/replays/utils'; import SearchBar from 'sentry/components/searchBar'; import {t} from 'sentry/locale'; import space from 'sentry/styles/space'; import type {BreadcrumbLevelType, BreadcrumbTypeDefault} from 'sentry/types/breadcrumbs'; import EmptyMessage from 'sentry/views/settings/components/emptyMessage'; import ConsoleMessage from './consoleMessage'; import {filterBreadcrumbs} from './utils'; interface Props { breadcrumbs: BreadcrumbTypeDefault[]; startTimestamp: number; } const getDistinctLogLevels = (breadcrumbs: BreadcrumbTypeDefault[]) => Array.from(new Set(breadcrumbs.map(breadcrumb => breadcrumb.level))); function Console({breadcrumbs, startTimestamp = 0}: Props) { const {currentHoverTime, currentTime} = useReplayContext(); const [searchTerm, setSearchTerm] = useState(''); const [logLevel, setLogLevel] = useState([]); const handleSearch = debounce(query => setSearchTerm(query), 150); const filteredBreadcrumbs = useMemo( () => filterBreadcrumbs(breadcrumbs, searchTerm, logLevel), [logLevel, searchTerm, breadcrumbs] ); const activeConsoleBounds = useMemo(() => { if (filteredBreadcrumbs.length <= 0 || currentHoverTime === undefined) { return [-1, -1]; } let indexUpperBound = 0; const finalBreadCrumbIndex = filteredBreadcrumbs.length - 1; const finalBreadcrumbTimestamp = filteredBreadcrumbs[finalBreadCrumbIndex].timestamp || ''; if (currentHoverTime >= relativeTimeInMs(finalBreadcrumbTimestamp, startTimestamp)) { indexUpperBound = finalBreadCrumbIndex; } else { indexUpperBound = filteredBreadcrumbs.findIndex( breadcrumb => relativeTimeInMs(breadcrumb.timestamp || '', startTimestamp) >= (currentHoverTime || 0) ) - 1; } const activeMessageBoundary = showPlayerTime( filteredBreadcrumbs[indexUpperBound]?.timestamp || '', startTimestamp ); const indexLowerBound = filteredBreadcrumbs.findIndex( breadcrumb => showPlayerTime(breadcrumb.timestamp || '', startTimestamp) === activeMessageBoundary ); return [indexLowerBound, indexUpperBound]; }, [currentHoverTime, filteredBreadcrumbs, startTimestamp]); return ( ({ value: breadcrumbLogLevel, label: breadcrumbLogLevel, }))} onChange={selections => setLogLevel(selections.map(selection => selection.value)) } /> {filteredBreadcrumbs.length > 0 ? ( {filteredBreadcrumbs.map((breadcrumb, i) => { return ( = activeConsoleBounds[0] && i <= activeConsoleBounds[1]} startTimestamp={startTimestamp} key={i} isLast={i === breadcrumbs.length - 1} breadcrumb={breadcrumb} hasOccurred={ currentTime >= relativeTimeInMs(breadcrumb?.timestamp || '', startTimestamp) } /> ); })} ) : ( )} ); } 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 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; `; export default Console;