Files
2024-02-15 17:01:50 +00:00

126 lines
3.8 KiB
TypeScript

import {useRef} from 'react';
import styled from '@emotion/styled';
import ErrorBoundary from 'sentry/components/errorBoundary';
import Placeholder from 'sentry/components/placeholder';
import QuestionTooltip from 'sentry/components/questionTooltip';
import {t} from 'sentry/locale';
import {space} from 'sentry/styles/space';
import type {Event} from 'sentry/types';
import useRouteAnalyticsParams from 'sentry/utils/routeAnalytics/useRouteAnalyticsParams';
import {useDimensions} from 'sentry/utils/useDimensions';
import useOrganization from 'sentry/utils/useOrganization';
import {hasTraceTimelineFeature} from 'sentry/views/issueDetails/traceTimeline/utils';
import {TraceTimelineEvents} from './traceTimelineEvents';
import {useTraceTimelineEvents} from './useTraceTimelineEvents';
interface TraceTimelineProps {
event: Event;
}
export function TraceTimeline({event}: TraceTimelineProps) {
const organization = useOrganization({allowNull: true});
const timelineRef = useRef<HTMLDivElement>(null);
const {width} = useDimensions({elementRef: timelineRef});
const hasFeature = hasTraceTimelineFeature(organization);
const {isError, isLoading, traceEvents} = useTraceTimelineEvents({event}, hasFeature);
const hasTraceId = !!event.contexts?.trace?.trace_id;
let timelineStatus: string | undefined;
if (hasFeature) {
if (hasTraceId && !isLoading) {
timelineStatus = traceEvents.length > 1 ? 'shown' : 'empty';
} else if (!hasTraceId) {
timelineStatus = 'no_trace_id';
}
}
useRouteAnalyticsParams(timelineStatus ? {trace_timeline_status: timelineStatus} : {});
if (!hasFeature || !hasTraceId) {
return null;
}
const noEvents = !isLoading && traceEvents.length === 0;
// Timelines with only the current event are not useful
const onlySelfEvent =
!isLoading &&
traceEvents.length > 0 &&
traceEvents.every(item => item.id === event.id);
if (isError || noEvents || onlySelfEvent) {
// display empty placeholder to reduce layout shift
return <div style={{height: 38}} data-test-id="trace-timeline-empty" />;
}
return (
<ErrorBoundary mini>
<TimelineWrapper>
<div ref={timelineRef}>
{isLoading ? (
<LoadingSkeleton>
<Placeholder height="14px" />
<Placeholder height="8px" />
</LoadingSkeleton>
) : (
<TimelineEventsContainer>
<TimelineOutline />
{/* Sets a min width of 200 for testing */}
<TraceTimelineEvents event={event} width={Math.max(width, 200)} />
</TimelineEventsContainer>
)}
</div>
<QuestionTooltipWrapper>
<QuestionTooltip
size="sm"
title={t(
'This is a trace timeline showing all related events happening upstream and downstream of this event'
)}
position="bottom"
/>
</QuestionTooltipWrapper>
</TimelineWrapper>
</ErrorBoundary>
);
}
const TimelineWrapper = styled('div')`
display: grid;
grid-template-columns: 1fr auto;
align-items: start;
gap: ${space(2)};
margin-top: ${space(0.5)};
`;
const QuestionTooltipWrapper = styled('div')`
margin-top: ${space(0.25)};
`;
/**
* Displays the container the dots appear inside of
*/
const TimelineOutline = styled('div')`
position: absolute;
left: 0;
top: 3.5px;
width: 100%;
height: 10px;
border: 1px solid ${p => p.theme.innerBorder};
border-radius: ${p => p.theme.borderRadius};
background-color: ${p => p.theme.backgroundSecondary};
`;
const TimelineEventsContainer = styled('div')`
position: relative;
height: 34px;
padding-top: 10px;
`;
const LoadingSkeleton = styled('div')`
display: flex;
flex-direction: column;
gap: ${space(0.25)};
padding: ${space(0.5)} 0 ${space(1)};
height: 34px;
`;