traceTimeline.tsx 3.1 KB

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