import {Fragment, useRef} from 'react'; import styled from '@emotion/styled'; import ProjectAvatar from 'sentry/components/avatar/projectAvatar'; import {pickBarColor} from 'sentry/components/performance/waterfall/utils'; import {space} from 'sentry/styles/space'; import type {Project} from 'sentry/types'; import toPercent from 'sentry/utils/number/toPercent'; import toPixels from 'sentry/utils/number/toPixels'; import type {TraceFullDetailed} from 'sentry/utils/performance/quickTrace/types'; import {useDimensions} from 'sentry/utils/useDimensions'; import useProjects from 'sentry/utils/useProjects'; import ResizeableContainer from 'sentry/views/replays/detail/perfTable/resizeableContainer'; import type {FlattenedTrace} from 'sentry/views/replays/detail/perfTable/useReplayPerfData'; import useVirtualScrolling from 'sentry/views/replays/detail/perfTable/useVirtualScrolling'; const EMDASH = '\u2013'; interface Props { flattenedTrace: FlattenedTrace; onDimensionChange: () => void; } export default function TraceGrid({flattenedTrace, onDimensionChange}: Props) { const measureRef = useRef(null); const {width} = useDimensions({elementRef: measureRef}); const scrollableWindowRef = useRef(null); const scrollableContentRef = useRef(null); const {offsetX, reclamp: adjustScrollPosition} = useVirtualScrolling({ windowRef: scrollableWindowRef, contentRef: scrollableContentRef, }); const hasSize = width > 0; return ( {hasSize ? ( { adjustScrollPosition(); onDimensionChange(); }} > ) : null} ); } function SpanNameList({flattenedTrace}: {flattenedTrace: FlattenedTrace}) { const {projects} = useProjects(); return ( {flattenedTrace.map(flattened => { const project = projects.find(p => p.id === String(flattened.trace.project_id)); const labelStyle = { paddingLeft: `calc(${space(2)} * ${flattened.indent})`, }; return ( {flattened.trace['transaction.op']} {EMDASH} {flattened.trace.transaction} ); })} ); } function SpanDurations({flattenedTrace}: {flattenedTrace: FlattenedTrace}) { const traces = flattenedTrace.map(flattened => flattened.trace); const startTimestampMs = Math.min(...traces.map(trace => trace.start_timestamp)) * 1000; const endTimestampMs = Math.max( ...traces.map(trace => trace.start_timestamp * 1000 + trace['transaction.duration']) ); return ( {flattenedTrace.map(flattened => ( {flattened.trace['transaction.duration']}ms ))} ); } function barCSSPosition( startTimestampMs: number, endTimestampMs: number, trace: TraceFullDetailed ) { const fullDuration = Math.abs(endTimestampMs - startTimestampMs) || 1; const sinceStart = trace.start_timestamp * 1000 - startTimestampMs; const duration = trace['transaction.duration']; return { left: toPercent(sinceStart / fullDuration), width: toPercent(duration / fullDuration), }; } const TwoColumns = styled('div')` display: grid; grid-template-columns: 1fr max-content; `; const Relative = styled('div')` position: relative; `; const OverflowHidden = styled('div')` overflow: hidden; `; const TxnList = styled('div')` font-size: ${p => p.theme.fontSizeRelativeSmall}; & > :nth-child(2n + 1) { background: ${p => p.theme.backgroundTertiary}; } `; const TxnCell = styled('div')` position: relative; display: flex; align-items: center; justify-self: auto; padding: ${space(0.25)} ${space(0.5)}; overflow: hidden; `; const TxnLabel = styled('div')` display: flex; gap: ${space(0.5)}; align-items: center; white-space: nowrap; `; const TxnDuration = styled('div')` display: flex; flex: 1 1 auto; align-items: center; justify-content: flex-end; `; const TxnDurationBar = styled('div')` position: absolute; content: ''; top: 50%; transform: translate(0, -50%); height: ${space(1.5)}; margin-block: ${space(0.25)}; user-select: none; min-width: 1px; `;