import {Fragment, LegacyRef, useEffect, useRef} from 'react'; import Count from 'sentry/components/count'; import { Row, RowCell, RowCellContainer, } from 'sentry/components/performance/waterfall/row'; import { DividerContainer, DividerLine, DividerLineGhostContainer, } from 'sentry/components/performance/waterfall/rowDivider'; import { RowTitle, RowTitleContainer, SpanGroupRowTitleContent, } from 'sentry/components/performance/waterfall/rowTitle'; import { TOGGLE_BORDER_BOX, TreeToggle, TreeToggleContainer, } from 'sentry/components/performance/waterfall/treeConnector'; import {toPercent} from 'sentry/components/performance/waterfall/utils'; import {EventTransaction} from 'sentry/types/event'; import {defined} from 'sentry/utils'; import * as AnchorLinkManager from './anchorLinkManager'; import * as DividerHandlerManager from './dividerHandlerManager'; import SpanBarCursorGuide from './spanBarCursorGuide'; import {MeasurementMarker} from './styles'; import {EnhancedSpan, ProcessedSpanType} from './types'; import { getMeasurementBounds, getMeasurements, SpanBoundsType, SpanGeneratedBoundsType, spanTargetHash, } from './utils'; const MARGIN_LEFT = 0; type Props = { event: Readonly; generateBounds: (bounds: SpanBoundsType) => SpanGeneratedBoundsType; generateContentSpanBarRef: () => (instance: HTMLDivElement | null) => void; onWheel: (deltaX: number) => void; renderGroupSpansTitle: () => React.ReactNode; renderSpanRectangles: () => React.ReactNode; renderSpanTreeConnector: () => React.ReactNode; span: Readonly; spanGrouping: EnhancedSpan[]; spanNumber: number; toggleSpanGroup: () => void; treeDepth: number; }; function renderGroupedSpansToggler(props: Props) { const {treeDepth, spanGrouping, renderSpanTreeConnector, toggleSpanGroup} = props; const left = treeDepth * (TOGGLE_BORDER_BOX / 2) + MARGIN_LEFT; return ( {renderSpanTreeConnector()} { event.stopPropagation(); toggleSpanGroup(); }} > ); } function renderDivider( dividerHandlerChildrenProps: DividerHandlerManager.DividerHandlerManagerChildrenProps ) { const {addDividerLineRef} = dividerHandlerChildrenProps; return ( { dividerHandlerChildrenProps.setHover(true); }} onMouseLeave={() => { dividerHandlerChildrenProps.setHover(false); }} onMouseOver={() => { dividerHandlerChildrenProps.setHover(true); }} onMouseDown={dividerHandlerChildrenProps.onDragStart} onClick={event => { // we prevent the propagation of the clicks from this component to prevent // the span detail from being opened. event.stopPropagation(); }} /> ); } function renderMeasurements( event: Readonly, generateBounds: (bounds: SpanBoundsType) => SpanGeneratedBoundsType ) { const measurements = getMeasurements(event, generateBounds); return ( {Array.from(measurements).map(([timestamp, verticalMark]) => { const bounds = getMeasurementBounds(timestamp, generateBounds); const shouldDisplay = defined(bounds.left) && defined(bounds.width); if (!shouldDisplay || !bounds.isSpanVisibleInView) { return null; } return ( ); })} ); } export function SpanGroupBar(props: Props) { const spanTitleRef: LegacyRef | null = useRef(null); const {onWheel} = props; useEffect(() => { const currentRef = spanTitleRef.current; const handleWheel = (event: WheelEvent) => { if (Math.abs(event.deltaY) > Math.abs(event.deltaX)) { return; } event.preventDefault(); event.stopPropagation(); if (Math.abs(event.deltaY) === Math.abs(event.deltaX)) { return; } onWheel(event.deltaX); }; if (currentRef) { currentRef.addEventListener('wheel', handleWheel, { passive: false, }); } return () => { if (currentRef) { currentRef.removeEventListener('wheel', handleWheel); } }; }, [onWheel]); return ( {( dividerHandlerChildrenProps: DividerHandlerManager.DividerHandlerManagerChildrenProps ) => { const { generateBounds, toggleSpanGroup, span, treeDepth, spanNumber, event, spanGrouping, } = props; const {isSpanVisibleInView: isSpanVisible} = generateBounds({ startTimestamp: span.start_timestamp, endTimestamp: span.timestamp, }); const {dividerPosition, addGhostDividerLineRef} = dividerHandlerChildrenProps; const {generateContentSpanBarRef} = props; const left = treeDepth * (TOGGLE_BORDER_BOX / 2) + MARGIN_LEFT; return ( {({registerScrollFn}) => { spanGrouping.forEach(spanObj => { registerScrollFn( spanTargetHash(spanObj.span.span_id), toggleSpanGroup, true ); }); return ( props.toggleSpanGroup()} ref={spanTitleRef} > {renderGroupedSpansToggler(props)} {props.renderGroupSpansTitle()} {renderDivider(dividerHandlerChildrenProps)} props.toggleSpanGroup()} > {props.renderSpanRectangles()} {renderMeasurements(event, generateBounds)} { // the ghost divider line should not be interactive. // we prevent the propagation of the clicks from this component to prevent // the span detail from being opened. e.stopPropagation(); }} /> ); }} ); }} ); }