mirror of
https://github.com/hanzoai/sentinel.git
synced 2026-09-29 22:34:33 +00:00
126 lines
3.8 KiB
TypeScript
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;
|
|
`;
|