123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157 |
- 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<Crumb, BreadcrumbTypeDefault>[];
- startTimestampMs: number;
- }
- function Console({breadcrumbs, startTimestampMs = 0}: Props) {
- const {currentHoverTime, currentTime} = useReplayContext();
- const containerRef = useRef<HTMLDivElement>(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 (
- <ConsoleContainer>
- <ConsoleFilters>
- <CompactSelect
- triggerProps={{prefix: t('Log Level')}}
- triggerLabel={logLevel.length === 0 ? t('Any') : null}
- multiple
- options={getLogLevels(breadcrumbs, logLevel).map(value => ({
- value,
- label: value,
- }))}
- onChange={selected => setLogLevel(selected.map(_ => _.value))}
- size="sm"
- value={logLevel}
- />
- <SearchBar
- onChange={setSearchTerm}
- placeholder={t('Search console logs...')}
- size="sm"
- query={searchTerm}
- />
- </ConsoleFilters>
- <ConsoleMessageContainer ref={containerRef}>
- {items.length > 0 ? (
- <ConsoleTable>
- {items.map((breadcrumb, i) => {
- return (
- <ConsoleMessage
- isActive={closestUserAction?.id === breadcrumb.id}
- isCurrent={currentUserAction?.id === breadcrumb.id}
- isOcurring={isOcurring(breadcrumb, closestUserAction)}
- startTimestampMs={startTimestampMs}
- key={breadcrumb.id}
- isLast={i === breadcrumbs.length - 1}
- breadcrumb={breadcrumb}
- hasOccurred={
- currentTime >=
- relativeTimeInMs(breadcrumb?.timestamp || '', startTimestampMs)
- }
- />
- );
- })}
- </ConsoleTable>
- ) : (
- <StyledEmptyMessage title={t('No results found.')} />
- )}
- </ConsoleMessageContainer>
- </ConsoleContainer>
- );
- }
- 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;
|