traceTimeline.tsx 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116
  1. import {useRef} from 'react';
  2. import styled from '@emotion/styled';
  3. import ErrorBoundary from 'sentry/components/errorBoundary';
  4. import Placeholder from 'sentry/components/placeholder';
  5. import {space} from 'sentry/styles/space';
  6. import type {Event} from 'sentry/types';
  7. import useRouteAnalyticsParams from 'sentry/utils/routeAnalytics/useRouteAnalyticsParams';
  8. import {useDimensions} from 'sentry/utils/useDimensions';
  9. import useOrganization from 'sentry/utils/useOrganization';
  10. import {hasTraceTimelineFeature} from 'sentry/views/issueDetails/traceTimeline/utils';
  11. import {TraceTimelineEvents} from './traceTimelineEvents';
  12. import {useTraceTimelineEvents} from './useTraceTimelineEvents';
  13. interface TraceTimelineProps {
  14. event: Event;
  15. }
  16. export function TraceTimeline({event}: TraceTimelineProps) {
  17. const organization = useOrganization({allowNull: true});
  18. const timelineRef = useRef<HTMLDivElement>(null);
  19. const {width} = useDimensions({elementRef: timelineRef});
  20. const hasFeature = hasTraceTimelineFeature(organization);
  21. const {isError, isLoading, data} = useTraceTimelineEvents({event}, hasFeature);
  22. const hasTraceId = !!event.contexts?.trace?.trace_id;
  23. let timelineStatus: string | undefined;
  24. if (hasFeature) {
  25. if (hasTraceId && !isLoading) {
  26. timelineStatus = data.length > 1 ? 'shown' : 'empty';
  27. } else if (!hasTraceId) {
  28. timelineStatus = 'no_trace_id';
  29. }
  30. }
  31. useRouteAnalyticsParams(timelineStatus ? {trace_timeline_status: timelineStatus} : {});
  32. if (!hasFeature || !hasTraceId) {
  33. return null;
  34. }
  35. const noEvents = !isLoading && data.length === 0;
  36. // Timelines with only the current event are not useful
  37. const onlySelfEvent =
  38. !isLoading && data.length > 0 && data.every(item => item.id === event.id);
  39. if (isError || noEvents || onlySelfEvent) {
  40. // display empty placeholder to reduce layout shift
  41. return <div style={{height: 38}} data-test-id="trace-timeline-empty" />;
  42. }
  43. return (
  44. <ErrorBoundary mini>
  45. <Stacked ref={timelineRef}>
  46. {isLoading ? (
  47. <LoadingSkeleton>
  48. <Placeholder height="14px" />
  49. <Placeholder height="8px" />
  50. </LoadingSkeleton>
  51. ) : (
  52. <TimelineEventsContainer>
  53. <TimelineOutline />
  54. {/* Sets a min width of 200 for testing */}
  55. <TraceTimelineEvents event={event} width={Math.max(width, 200)} />
  56. </TimelineEventsContainer>
  57. )}
  58. </Stacked>
  59. </ErrorBoundary>
  60. );
  61. }
  62. /**
  63. * Displays the container the dots appear inside of
  64. */
  65. const TimelineOutline = styled('div')`
  66. position: absolute;
  67. left: 0;
  68. top: 3.5px;
  69. width: 100%;
  70. height: 10px;
  71. border: 1px solid ${p => p.theme.innerBorder};
  72. border-radius: ${p => p.theme.borderRadius};
  73. background-color: ${p => p.theme.backgroundSecondary};
  74. `;
  75. /**
  76. * Render all child elements directly on top of each other.
  77. *
  78. * This implementation does not remove the stack of elements from the document
  79. * flow, so width/height is reserved.
  80. *
  81. * An alternative would be to use `position:absolute;` in which case the size
  82. * would not be part of document flow and other elements could render behind.
  83. */
  84. const Stacked = styled('div')`
  85. display: grid;
  86. grid-template: 1fr / 1fr;
  87. > * {
  88. grid-area: 1 / 1;
  89. }
  90. margin-top: ${space(0.5)};
  91. `;
  92. const TimelineEventsContainer = styled('div')`
  93. position: relative;
  94. height: 34px;
  95. padding-top: 10px;
  96. `;
  97. const LoadingSkeleton = styled('div')`
  98. display: flex;
  99. flex-direction: column;
  100. gap: ${space(0.25)};
  101. padding: ${space(0.75)} 0 ${space(1)};
  102. height: 34px;
  103. `;