traceTimeline.tsx 4.0 KB

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