traceTimeline.tsx 2.9 KB

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