import {CSSProperties, useCallback} from 'react'; import styled from '@emotion/styled'; import ErrorBoundary from 'sentry/components/errorBoundary'; import {useReplayContext} from 'sentry/components/replays/replayContext'; import {relativeTimeInMs} from 'sentry/components/replays/utils'; import {IconFire, IconWarning} from 'sentry/icons'; import space from 'sentry/styles/space'; import type {BreadcrumbTypeDefault, Crumb} from 'sentry/types/breadcrumbs'; import {getPrevReplayEvent} from 'sentry/utils/replays/getReplayEvent'; import useCrumbHandlers from 'sentry/utils/replays/hooks/useCrumbHandlers'; import MessageFormatter from 'sentry/views/replays/detail/console/messageFormatter'; import {breadcrumbHasIssue} from 'sentry/views/replays/detail/console/utils'; import ViewIssueLink from 'sentry/views/replays/detail/console/viewIssueLink'; import TimestampButton from 'sentry/views/replays/detail/timestampButton'; type Props = { breadcrumb: Extract; breadcrumbs: Extract[]; startTimestampMs: number; style: CSSProperties; }; function ConsoleMessage({breadcrumb, breadcrumbs, startTimestampMs, style}: Props) { const {currentTime, currentHoverTime} = useReplayContext(); const {handleMouseEnter, handleMouseLeave, handleClick} = useCrumbHandlers(startTimestampMs); const onClickTimestamp = useCallback( () => handleClick(breadcrumb), [handleClick, breadcrumb] ); const onMouseEnter = useCallback( () => handleMouseEnter(breadcrumb), [handleMouseEnter, breadcrumb] ); const onMouseLeave = useCallback( () => handleMouseLeave(breadcrumb), [handleMouseLeave, breadcrumb] ); const current = getPrevReplayEvent({ items: breadcrumbs, targetTimestampMs: startTimestampMs + currentTime, allowEqual: true, allowExact: true, }); const hovered = currentHoverTime ? getPrevReplayEvent({ items: breadcrumbs, targetTimestampMs: startTimestampMs + currentHoverTime, allowEqual: true, allowExact: true, }) : undefined; const hasOccurred = currentTime >= relativeTimeInMs(breadcrumb.timestamp || 0, startTimestampMs); const isCurrent = breadcrumb.id === current?.id; const isHovered = breadcrumb.id === hovered?.id; return ( {breadcrumbHasIssue(breadcrumb) ? ( ) : null} ); } const IssueLinkWrapper = styled('div')` float: right; `; const ConsoleLog = styled('div')<{ hasOccurred: boolean; isCurrent: boolean; isHovered: boolean; level: string; }>` display: grid; grid-template-columns: 12px 1fr max-content; gap: ${space(0.75)}; padding: ${space(0.5)} ${space(1)}; background-color: ${p => ['warning', 'error'].includes(p.level) ? p.theme.alert[p.level].backgroundLight : 'inherit'}; border-bottom: 1px solid ${p => p.isCurrent ? p.theme.purple300 : p.isHovered ? p.theme.purple200 : 'transparent'}; color: ${p => ['warning', 'error'].includes(p.level) ? p.theme.alert[p.level].iconColor : p.hasOccurred ? 'inherit' : p.theme.gray300}; /* Show the timestamp button "Play" icon when we hover the row. This is a really generic selector that could find many things, but for now it only targets the one thing that we expect. */ &:hover button > svg { visibility: visible; } `; const ICONS = { error: , warning: , }; function Icon({level}: {level: Extract['level']}) { return {ICONS[level]}; } const Message = styled('div')` font-family: ${p => p.theme.text.familyMono}; font-size: ${p => p.theme.fontSizeSmall}; white-space: pre-wrap; word-break: break-word; `; export default ConsoleMessage;