Skip to content

Commit b0d3cae

Browse files
Add Show spans/Show logs visibility toggles to trace waterfall panel
Users can now independently show/hide spans and log events in the trace waterfall using dedicated checkboxes. The 'Show spans' checkbox is always visible; the 'Show logs' checkbox appears when a log source is configured. Filtering is applied client-side after the DAG flattening step, so the toggle state has no effect on data fetching. Counts in the header update to reflect only visible rows. Co-authored-by: Mike Shi <mike@hyperdx.io>
1 parent 5c6da48 commit b0d3cae

2 files changed

Lines changed: 83 additions & 7 deletions

File tree

packages/app/src/components/DBTraceWaterfallChart.tsx

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -610,6 +610,8 @@ export function DBTraceWaterfallChartContainer({
610610

611611
const [collapsedIds, setCollapsedIds] = useState<Set<string>>(new Set());
612612
const [showSpanEvents, setShowSpanEvents] = useState(true);
613+
const [showSpans, setShowSpans] = useState(true);
614+
const [showLogs, setShowLogs] = useState(true);
613615

614616
const { nodesMap, flattenedNodes } = useMemo(() => {
615617
const rootNodes: Node[] = [];
@@ -737,8 +739,16 @@ export function DBTraceWaterfallChartContainer({
737739
[nodesMap],
738740
);
739741

740-
const spanCount = flattenedNodes.length;
741-
const errorCount = flattenedNodes.filter(
742+
const visibleNodes = useMemo(() => {
743+
if (showSpans && showLogs) return flattenedNodes;
744+
return flattenedNodes.filter(node => {
745+
if (node.type === SourceKind.Log) return showLogs;
746+
return showSpans;
747+
});
748+
}, [flattenedNodes, showSpans, showLogs]);
749+
750+
const spanCount = visibleNodes.length;
751+
const errorCount = visibleNodes.filter(
742752
node =>
743753
node.StatusCode === 'Error' ||
744754
node.SeverityText?.toLowerCase() === 'error',
@@ -763,7 +773,7 @@ export function DBTraceWaterfallChartContainer({
763773

764774
const timelineRows = useMemo(
765775
() =>
766-
flattenedNodes.map((result, i) => {
776+
visibleNodes.map((result, i) => {
767777
const tookMs = (result.Duration || 0) * 1000;
768778
const startOffset = new Date(result.Timestamp).getTime();
769779
const start = startOffset - minOffset;
@@ -940,7 +950,7 @@ export function DBTraceWaterfallChartContainer({
940950
}),
941951
[
942952
collapsedIds,
943-
flattenedNodes,
953+
visibleNodes,
944954
formatTime,
945955
highlightedRowWhere,
946956
isFilterActive,
@@ -952,8 +962,7 @@ export function DBTraceWaterfallChartContainer({
952962
setCollapseTooltipShown,
953963
],
954964
);
955-
// TODO: Highlighting support
956-
const initialScrollRowIndex = flattenedNodes.findIndex(v => {
965+
const initialScrollRowIndex = visibleNodes.findIndex(v => {
957966
return v.id === highlightedRowWhere;
958967
});
959968

@@ -1013,6 +1022,22 @@ export function DBTraceWaterfallChartContainer({
10131022
{errorCountString}
10141023
</span>
10151024
</Text>
1025+
<Checkbox
1026+
size="xs"
1027+
label="Show spans"
1028+
checked={showSpans}
1029+
onChange={() => setShowSpans(!showSpans)}
1030+
data-testid="show-spans-checkbox"
1031+
/>
1032+
{logTableSource && (
1033+
<Checkbox
1034+
size="xs"
1035+
label="Show logs"
1036+
checked={showLogs}
1037+
onChange={() => setShowLogs(!showLogs)}
1038+
data-testid="show-logs-checkbox"
1039+
/>
1040+
)}
10161041
<Checkbox
10171042
size="xs"
10181043
label="Show span events"
@@ -1071,7 +1096,7 @@ export function DBTraceWaterfallChartContainer({
10711096
<div>
10721097
An unknown error occurred. <ContactSupportText />
10731098
</div>
1074-
) : flattenedNodes.length === 0 ? (
1099+
) : visibleNodes.length === 0 ? (
10751100
(emptyState ?? (
10761101
<div className="my-3">No matching spans or logs found</div>
10771102
))

packages/app/src/components/__tests__/DBTraceWaterfallChart.test.tsx

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
} from '@hyperdx/common-utils/dist/types';
77
import { screen, waitFor } from '@testing-library/react';
88
import { renderHook } from '@testing-library/react';
9+
import userEvent from '@testing-library/user-event';
910

1011
import { TimelineChart } from '@/components/TimelineChart';
1112
import useOffsetPaginatedQuery from '@/hooks/useOffsetPaginatedQuery';
@@ -310,6 +311,56 @@ describe('DBTraceWaterfallChartContainer', () => {
310311
screen.getByText('http span https://api.example.com/users'),
311312
).toBeInTheDocument();
312313
});
314+
315+
it('renders Show spans and Show logs checkboxes when log source is present', async () => {
316+
setupQueryMocks({ traceData: mockTraceData, logData: mockLogData });
317+
renderComponent();
318+
await waitForLoading();
319+
320+
expect(screen.getByTestId('show-spans-checkbox')).toBeInTheDocument();
321+
expect(screen.getByTestId('show-logs-checkbox')).toBeInTheDocument();
322+
});
323+
324+
it('does not render Show logs checkbox when no log source', async () => {
325+
setupQueryMocks({ traceData: mockTraceData });
326+
renderComponent(null);
327+
await waitForLoading();
328+
329+
expect(screen.getByTestId('show-spans-checkbox')).toBeInTheDocument();
330+
expect(screen.queryByTestId('show-logs-checkbox')).not.toBeInTheDocument();
331+
});
332+
333+
it('hides log rows when Show logs is unchecked', async () => {
334+
const user = userEvent.setup();
335+
setupQueryMocks({ traceData: mockTraceData, logData: mockLogData });
336+
renderComponent();
337+
await waitForLoading();
338+
339+
expect(MockTimelineChart.latestProps.rows.length).toBe(2);
340+
341+
const showLogsCheckbox = screen.getByTestId('show-logs-checkbox');
342+
await user.click(showLogsCheckbox);
343+
344+
await waitFor(() => {
345+
expect(MockTimelineChart.latestProps.rows.length).toBe(1);
346+
});
347+
});
348+
349+
it('hides span rows when Show spans is unchecked', async () => {
350+
const user = userEvent.setup();
351+
setupQueryMocks({ traceData: mockTraceData, logData: mockLogData });
352+
renderComponent();
353+
await waitForLoading();
354+
355+
expect(MockTimelineChart.latestProps.rows.length).toBe(2);
356+
357+
const showSpansCheckbox = screen.getByTestId('show-spans-checkbox');
358+
await user.click(showSpansCheckbox);
359+
360+
await waitFor(() => {
361+
expect(MockTimelineChart.latestProps.rows.length).toBe(1);
362+
});
363+
});
313364
});
314365

315366
describe('useEventsAroundFocus', () => {

0 commit comments

Comments
 (0)