Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions src/Routes/Tables/Jobs/Trace/ModernTraceViewer.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import TraceTimeline from './TraceTimeline';
import TraceTimelineMinimap from './TraceTimelineMinimap';
import SpanRow from './SpanRow';
import TraceLogsModal from './TraceLogsModal';
import { useTraceRowHeight } from './useTraceRowHeight';
import { getCurrentTheme, getSystemColors } from './traceConstants';

/*
Expand Down Expand Up @@ -80,6 +81,7 @@ const ModernTraceViewer = ({ data }) => {
const [minimapMode, setMinimapMode] = useState('highlight');
const [isDark, setIsDark] = useState(getCurrentTheme() === 'DARK');
const [logsModalContext, setLogsModalContext] = useState(null);
const { rowHeight, onRowHeightChange } = useTraceRowHeight();

const {
kibanaUrl,
Expand Down Expand Up @@ -224,9 +226,9 @@ const ModernTraceViewer = ({ data }) => {
nodeRect.top - containerRect.top + container.scrollTop;

const containerHeight = container.clientHeight;
const rowHeight = node.offsetHeight;
const currentRowHeight = node.offsetHeight;
const scrollTarget =
rowTopRelativeToContainer - containerHeight / 2 + rowHeight / 2;
rowTopRelativeToContainer - containerHeight / 2 + currentRowHeight / 2;

container.scrollTo({
top: Math.max(0, scrollTarget),
Expand Down Expand Up @@ -343,6 +345,8 @@ const ModernTraceViewer = ({ data }) => {
onOpenLogs={handleOpenLogs}
onOpenKibana={handleOpenKibana}
isKibanaConfigured={Boolean(kibanaUrl)}
rowHeight={rowHeight}
onRowHeightChange={onRowHeightChange}
rowRef={node => registerSpanRef(span.spanID, node)}
/>
))}
Expand Down
36 changes: 32 additions & 4 deletions src/Routes/Tables/Jobs/Trace/SpanRow.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
DEPTH_INDENT,
MAX_DEPTH_INDENT,
} from './traceConstants';
import { useTraceRowResize } from './useTraceRowHeight';

const { Title, Text } = Typography;

Expand Down Expand Up @@ -75,7 +76,7 @@ const SpanNameWrapper = styled.div`
padding: 8px 12px;
display: flex;
align-items: center;
min-height: 36px;
min-height: ${props => props.$rowHeight}px;
font-size: 14px;
border-right: 1px solid
${props => {
Expand Down Expand Up @@ -178,7 +179,7 @@ const OperationText = styled(Text)`
const SpanBarContainer = styled.div`
flex: 1;
margin: 0 12px;
height: 36px;
height: ${props => props.$rowHeight}px;
display: flex;
align-items: center;
position: relative;
Expand All @@ -192,7 +193,7 @@ const SpanBarContainer = styled.div`

const SpanBarTrack = styled.div`
width: 100%;
height: 24px;
height: ${props => Math.max(props.$rowHeight - 12, 14)}px;
background: ${props => {
const colors = getSystemColors(props.$isDark);
return props.$isDark ? '#1a2332' : colors.lightGrey;
Expand Down Expand Up @@ -388,6 +389,16 @@ const KibanaIconWrap = styled.span`
font-size: 17px;
`;

const RowResizeHandle = styled.div`
position: absolute;
left: 0;
right: 0;
bottom: 0;
height: 6px;
cursor: row-resize;
z-index: 5;
`;

const getTagValue = (tags, keys) => {
if (!Array.isArray(tags)) {
return '';
Expand Down Expand Up @@ -614,6 +625,8 @@ const SpanRow = ({
onOpenLogs,
onOpenKibana,
isKibanaConfigured,
rowHeight,
onRowHeightChange,
}) => {
const [isHovered, setIsHovered] = useState(false);
const [isTimelineHovered, setIsTimelineHovered] = useState(false);
Expand All @@ -639,6 +652,11 @@ const SpanRow = ({
const relativeStart = (span.relativeStartTime / totalDuration) * 100;
const width = Math.max((span.duration / totalDuration) * 100, 0.5);

const { startRowResize } = useTraceRowResize({
rowHeight,
onRowHeightChange,
});

const matchesSearch =
!searchTerm ||
span.operationName.toLowerCase().includes(searchTerm.toLowerCase()) ||
Expand Down Expand Up @@ -673,6 +691,7 @@ const SpanRow = ({
<SpanNameWrapper
$isHovered={isHovered}
$depth={depth}
$rowHeight={rowHeight}
$isDark={isDark}>
<SpanNameContent
onClick={() => onToggle(span.spanID)}
Expand Down Expand Up @@ -717,8 +736,9 @@ const SpanRow = ({
<SpanBarContainer
onMouseEnter={() => setIsTimelineHovered(true)}
onMouseLeave={() => setIsTimelineHovered(false)}
$rowHeight={rowHeight}
$isDark={isDark}>
<SpanBarTrack $isDark={isDark}>
<SpanBarTrack $isDark={isDark} $rowHeight={rowHeight}>
<SpanBar $left={relativeStart} $width={width} $color={color} />
<DurationLabel
$left={relativeStart}
Expand Down Expand Up @@ -848,6 +868,12 @@ const SpanRow = ({
)}
</LogsActions>
</RowContent>
<RowResizeHandle
role="separator"
aria-label="Resize trace rows"
aria-orientation="horizontal"
onMouseDown={startRowResize}
/>
</RowContainer>

{isExpanded && (
Expand Down Expand Up @@ -957,6 +983,8 @@ SpanRow.propTypes = {
onOpenLogs: PropTypes.func,
onOpenKibana: PropTypes.func,
isKibanaConfigured: PropTypes.bool,
rowHeight: PropTypes.number.isRequired,
onRowHeightChange: PropTypes.func.isRequired,
};

SpanRow.defaultProps = {
Expand Down
49 changes: 37 additions & 12 deletions src/Routes/Tables/Jobs/Trace/TimelineMarkers.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,20 @@ import {
getCurrentTheme,
getSystemColors,
NAME_COL_WIDTH,
NAME_COL_PADDING,
METRICS_COL_WIDTH,
LOGS_COL_WIDTH,
} from './traceConstants';

const { Text } = Typography;

/*
* The left padding must equal the name column width plus the left padding of
* the TimelineHeader so the time markers sit directly above the timeline bar
* area — not above the service/operation label column.
*
* Previously this was hardcoded to 266px (250 + 16). Now it is derived from
* the shared constants so a single change in traceConstants keeps everything
* aligned automatically.
* Mirror TraceTimeline's column structure so markers are rendered only inside
* the Timeline column width while staying aligned on responsive breakpoints.
*/
const MarkersContainer = styled.div`
display: flex;
justify-content: space-between;
padding: 8px ${NAME_COL_PADDING}px;
padding-left: ${NAME_COL_WIDTH + NAME_COL_PADDING}px;
align-items: center;
padding: 8px 16px;
background: ${props => {
const colors = getSystemColors(props.$isDark);
return colors.cardBackground;
Expand All @@ -37,6 +32,29 @@ const MarkersContainer = styled.div`
}};
`;

const ServiceSpacer = styled.div`
flex: 0 0 ${NAME_COL_WIDTH}px;
`;

const TimelineColumn = styled.div`
display: flex;
flex: 1;
justify-content: space-between;
min-width: 0;
`;

const MetricsSpacer = styled.div`
flex: 0 0 ${METRICS_COL_WIDTH}px;

@media (max-width: 500px) {
display: none;
}
`;

const LogsSpacer = styled.div`
flex: 0 0 ${LOGS_COL_WIDTH}px;
`;

const MarkerText = styled(Text)`
font-size: 11px;
color: ${props => (props.$isDark ? '#b8bfc7' : '#4a4a4a')};
Expand Down Expand Up @@ -72,7 +90,14 @@ const TimelineMarkers = ({ duration }) => {
);
}

return <MarkersContainer $isDark={isDark}>{markers}</MarkersContainer>;
return (
<MarkersContainer $isDark={isDark}>
<ServiceSpacer />
<TimelineColumn>{markers}</TimelineColumn>
<MetricsSpacer />
<LogsSpacer />
</MarkersContainer>
);
};

TimelineMarkers.propTypes = {
Expand Down
3 changes: 3 additions & 0 deletions src/Routes/Tables/Jobs/Trace/traceConstants.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,9 @@ export const LOGS_COL_WIDTH = 90;
export const DEPTH_INDENT = 20;
export const MAX_DEPTH_INDENT = 120; // caps at depth 6 effectively
export const SPAN_LIST_MIN_HEIGHT = 200;
export const DEFAULT_TRACE_ROW_HEIGHT = 36;
export const MIN_TRACE_ROW_HEIGHT = 28;
export const MAX_TRACE_ROW_HEIGHT = 72;

// Helper to detect current theme
export const getCurrentTheme = () => {
Expand Down
65 changes: 65 additions & 0 deletions src/Routes/Tables/Jobs/Trace/useTraceRowHeight.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { useState, useRef, useCallback, useEffect } from 'react';
import {
DEFAULT_TRACE_ROW_HEIGHT,
MIN_TRACE_ROW_HEIGHT,
MAX_TRACE_ROW_HEIGHT,
} from './traceConstants';

const clampRowHeight = nextHeight =>
Math.max(MIN_TRACE_ROW_HEIGHT, Math.min(MAX_TRACE_ROW_HEIGHT, nextHeight));

export const useTraceRowHeight = () => {
const [rowHeight, setRowHeight] = useState(DEFAULT_TRACE_ROW_HEIGHT);

const onRowHeightChange = useCallback(nextHeight => {
setRowHeight(clampRowHeight(nextHeight));
}, []);

return {
rowHeight,
onRowHeightChange,
};
};

export const useTraceRowResize = ({ rowHeight, onRowHeightChange }) => {
const resizeStateRef = useRef({ startY: 0, startHeight: rowHeight });

const onRowResize = useCallback(
event => {
const delta = event.clientY - resizeStateRef.current.startY;
onRowHeightChange(resizeStateRef.current.startHeight + delta);
},
[onRowHeightChange]
);

const stopRowResize = useCallback(() => {
window.removeEventListener('mousemove', onRowResize);
window.removeEventListener('mouseup', stopRowResize);
}, [onRowResize]);

const startRowResize = useCallback(
event => {
event.preventDefault();
event.stopPropagation();
resizeStateRef.current = {
startY: event.clientY,
startHeight: rowHeight,
};
window.addEventListener('mousemove', onRowResize);
window.addEventListener('mouseup', stopRowResize);
},
[onRowResize, rowHeight, stopRowResize]
);

useEffect(
() => () => {
window.removeEventListener('mousemove', onRowResize);
window.removeEventListener('mouseup', stopRowResize);
},
[onRowResize, stopRowResize]
);

return {
startRowResize,
};
};
Loading