traceTimeline.tsx 3.8 KB

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