traceTimeline.tsx 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
  1. import {Fragment, useRef} from 'react';
  2. import styled from '@emotion/styled';
  3. import ErrorBoundary from 'sentry/components/errorBoundary';
  4. import QuestionTooltip from 'sentry/components/questionTooltip';
  5. import {t} from 'sentry/locale';
  6. import {space} from 'sentry/styles/space';
  7. import type {Event} from 'sentry/types/event';
  8. import useRouteAnalyticsParams from 'sentry/utils/routeAnalytics/useRouteAnalyticsParams';
  9. import {useDimensions} from 'sentry/utils/useDimensions';
  10. import {TraceTimelineEvents} from './traceTimelineEvents';
  11. import {useTraceTimelineEvents} from './useTraceTimelineEvents';
  12. interface TraceTimelineProps {
  13. event: Event;
  14. }
  15. export function TraceTimeline({event}: TraceTimelineProps) {
  16. const timelineRef = useRef<HTMLDivElement>(null);
  17. const {width} = useDimensions({elementRef: timelineRef});
  18. const {isError, isLoading, traceEvents, oneOtherIssueEvent} = useTraceTimelineEvents({
  19. event,
  20. });
  21. const hasTraceId = !!event.contexts?.trace?.trace_id;
  22. let timelineStatus: string | undefined = 'empty';
  23. if (hasTraceId && !isLoading) {
  24. // When we have another issue we skip the timeline
  25. timelineStatus = oneOtherIssueEvent ? 'empty' : 'shown';
  26. } else if (!hasTraceId) {
  27. timelineStatus = 'no_trace_id';
  28. }
  29. useRouteAnalyticsParams({
  30. trace_timeline_status: timelineStatus,
  31. });
  32. if (!hasTraceId) {
  33. return null;
  34. }
  35. const noEvents = !isLoading && traceEvents.length === 0;
  36. // Timelines with only the current event are not useful
  37. const onlySelfEvent =
  38. !isLoading &&
  39. traceEvents.length > 0 &&
  40. traceEvents.every(item => item.id === event.id);
  41. if (isError || noEvents || onlySelfEvent || isLoading) {
  42. // display empty placeholder to reduce layout shift
  43. return null;
  44. }
  45. return (
  46. <ErrorBoundary mini>
  47. {timelineStatus === 'shown' && (
  48. <Fragment>
  49. <TimelineWrapper>
  50. <div ref={timelineRef}>
  51. <TimelineEventsContainer>
  52. <TimelineOutline />
  53. {/* Sets a min width of 200 for testing */}
  54. <TraceTimelineEvents event={event} width={Math.max(width, 200)} />
  55. </TimelineEventsContainer>
  56. </div>
  57. <QuestionTooltipWrapper>
  58. <QuestionTooltip
  59. size="sm"
  60. title={t(
  61. 'This is a trace timeline showing all related events happening upstream and downstream of this event'
  62. )}
  63. position="bottom"
  64. />
  65. </QuestionTooltipWrapper>
  66. </TimelineWrapper>
  67. </Fragment>
  68. )}
  69. </ErrorBoundary>
  70. );
  71. }
  72. const TimelineWrapper = styled('div')`
  73. display: grid;
  74. grid-template-columns: 1fr auto;
  75. align-items: start;
  76. gap: ${space(2)};
  77. margin-top: ${space(0.25)};
  78. `;
  79. const QuestionTooltipWrapper = styled('div')`
  80. margin-top: ${space(0.25)};
  81. `;
  82. /**
  83. * Displays the container the dots appear inside of
  84. */
  85. const TimelineOutline = styled('div')`
  86. position: absolute;
  87. left: 0;
  88. top: 3.5px;
  89. width: 100%;
  90. height: 10px;
  91. border: 1px solid ${p => p.theme.innerBorder};
  92. border-radius: ${p => p.theme.borderRadius};
  93. background-color: ${p => p.theme.backgroundSecondary};
  94. `;
  95. const TimelineEventsContainer = styled('div')`
  96. position: relative;
  97. height: 34px;
  98. padding-top: 10px;
  99. `;