traceTimeline.tsx 2.9 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
  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 {useUser} from 'sentry/utils/useUser';
  10. import {hasTraceTimelineFeature} from 'sentry/views/issueDetails/traceTimeline/utils';
  11. import {TraceTimelineEvents} from './traceTimelineEvents';
  12. import {useTraceTimelineEvents} from './useTraceTimelineEvents';
  13. const PLACEHOLDER_SIZE = '45px';
  14. interface TraceTimelineProps {
  15. event: Event;
  16. }
  17. export function TraceTimeline({event}: TraceTimelineProps) {
  18. const user = useUser();
  19. const organization = useOrganization({allowNull: true});
  20. const timelineRef = useRef<HTMLDivElement>(null);
  21. const {width} = useDimensions({elementRef: timelineRef});
  22. const hasFeature = hasTraceTimelineFeature(organization, user);
  23. const {isError, isLoading, data} = useTraceTimelineEvents({event}, hasFeature);
  24. if (!hasFeature || !event.contexts?.trace?.trace_id) {
  25. return null;
  26. }
  27. const noEvents = !isLoading && data.length === 0;
  28. // Timelines with only the current event are not useful
  29. const onlySelfEvent =
  30. !isLoading && data.length > 0 && data.every(item => item.id === event.id);
  31. if (isError || noEvents || onlySelfEvent) {
  32. // display empty placeholder to reduce layout shift
  33. return <div style={{height: PLACEHOLDER_SIZE}} data-test-id="trace-timeline-empty" />;
  34. }
  35. return (
  36. <ErrorBoundary mini>
  37. <Stacked ref={timelineRef}>
  38. {isLoading ? (
  39. <Placeholder height={PLACEHOLDER_SIZE} />
  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: 5px;
  58. width: 100%;
  59. height: 6px;
  60. border: 1px solid ${p => p.theme.innerBorder};
  61. border-radius: ${p => p.theme.borderRadius};
  62. `;
  63. /**
  64. * Render all child elements directly on top of each other.
  65. *
  66. * This implementation does not remove the stack of elements from the document
  67. * flow, so width/height is reserved.
  68. *
  69. * An alternative would be to use `position:absolute;` in which case the size
  70. * would not be part of document flow and other elements could render behind.
  71. */
  72. const Stacked = styled('div')`
  73. display: grid;
  74. grid-template: 1fr / 1fr;
  75. > * {
  76. grid-area: 1 / 1;
  77. }
  78. margin-top: ${space(1)};
  79. `;
  80. const TimelineEventsContainer = styled('div')`
  81. position: relative;
  82. height: 45px;
  83. padding-top: 10px;
  84. `;