123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125 |
- 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;
- `;
|