123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285 |
- 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<EventTransaction>;
- generateBounds: (bounds: SpanBoundsType) => SpanGeneratedBoundsType;
- generateContentSpanBarRef: () => (instance: HTMLDivElement | null) => void;
- onWheel: (deltaX: number) => void;
- renderGroupSpansTitle: () => React.ReactNode;
- renderSpanRectangles: () => React.ReactNode;
- renderSpanTreeConnector: () => React.ReactNode;
- span: Readonly<ProcessedSpanType>;
- 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 (
- <TreeToggleContainer style={{left: `${left}px`}} hasToggler>
- {renderSpanTreeConnector()}
- <TreeToggle
- disabled={false}
- isExpanded={false}
- errored={false}
- isSpanGroupToggler
- onClick={event => {
- event.stopPropagation();
- toggleSpanGroup();
- }}
- >
- <Count value={spanGrouping.length} />
- </TreeToggle>
- </TreeToggleContainer>
- );
- }
- function renderDivider(
- dividerHandlerChildrenProps: DividerHandlerManager.DividerHandlerManagerChildrenProps
- ) {
- const {addDividerLineRef} = dividerHandlerChildrenProps;
- return (
- <DividerLine
- ref={addDividerLineRef()}
- style={{
- position: 'absolute',
- }}
- onMouseEnter={() => {
- 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<EventTransaction>,
- generateBounds: (bounds: SpanBoundsType) => SpanGeneratedBoundsType
- ) {
- const measurements = getMeasurements(event, generateBounds);
- return (
- <Fragment>
- {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 (
- <MeasurementMarker
- key={String(timestamp)}
- style={{
- left: `clamp(0%, ${toPercent(bounds.left || 0)}, calc(100% - 1px))`,
- }}
- failedThreshold={verticalMark.failedThreshold}
- />
- );
- })}
- </Fragment>
- );
- }
- export function SpanGroupBar(props: Props) {
- const spanTitleRef: LegacyRef<HTMLDivElement> | 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 (
- <DividerHandlerManager.Consumer>
- {(
- 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 (
- <AnchorLinkManager.Consumer>
- {({registerScrollFn}) => {
- spanGrouping.forEach(spanObj => {
- registerScrollFn(
- spanTargetHash(spanObj.span.span_id),
- toggleSpanGroup,
- true
- );
- });
- return (
- <Row
- visible={isSpanVisible}
- showBorder={false}
- data-test-id={`span-row-${spanNumber}`}
- >
- <RowCellContainer>
- <RowCell
- data-type="span-row-cell"
- style={{
- width: `calc(${toPercent(dividerPosition)} - 0.5px)`,
- paddingTop: 0,
- }}
- onClick={() => props.toggleSpanGroup()}
- ref={spanTitleRef}
- >
- <RowTitleContainer ref={generateContentSpanBarRef()}>
- {renderGroupedSpansToggler(props)}
- <RowTitle
- style={{
- left: `${left}px`,
- width: '100%',
- }}
- >
- <SpanGroupRowTitleContent>
- {props.renderGroupSpansTitle()}
- </SpanGroupRowTitleContent>
- </RowTitle>
- </RowTitleContainer>
- </RowCell>
- <DividerContainer>
- {renderDivider(dividerHandlerChildrenProps)}
- </DividerContainer>
- <RowCell
- data-type="span-row-cell"
- showStriping={spanNumber % 2 !== 0}
- style={{
- width: `calc(${toPercent(1 - dividerPosition)} - 0.5px)`,
- }}
- onClick={() => props.toggleSpanGroup()}
- >
- {props.renderSpanRectangles()}
- {renderMeasurements(event, generateBounds)}
- <SpanBarCursorGuide />
- </RowCell>
- <DividerLineGhostContainer
- style={{
- width: `calc(${toPercent(dividerPosition)} + 0.5px)`,
- display: 'none',
- }}
- >
- <DividerLine
- ref={addGhostDividerLineRef()}
- style={{
- right: 0,
- }}
- className="hovering"
- onClick={e => {
- // 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();
- }}
- />
- </DividerLineGhostContainer>
- </RowCellContainer>
- </Row>
- );
- }}
- </AnchorLinkManager.Consumer>
- );
- }}
- </DividerHandlerManager.Consumer>
- );
- }
|