Skip to content

Commit f0823dc

Browse files
[web-shared][web] Fix trace viewer pagination issues (vercel#1182)
* [web-shared] Fix trace viewer pagination issue * [web-shared] Fix trace viewer pagination issue * [web-shared] Fix trace viewer pagination issue * [workflow o11y] bump package and fix trace viewer pagination issue * [workflow o11y] bump package and fix trace viewer pagination issue
1 parent a0b99c8 commit f0823dc

7 files changed

Lines changed: 362 additions & 43 deletions

File tree

.changeset/moody-ghosts-decide.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@workflow/web-shared": patch
3+
"@workflow/web": patch
4+
---
5+
6+
Fix traceviewer pagination issues

packages/web-shared/src/components/run-trace-view.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ interface RunTraceViewProps {
2727
onCancelRun?: (runId: string) => Promise<void>;
2828
onStreamClick?: (streamId: string) => void;
2929
onSpanSelect?: (info: SpanSelectionInfo) => void;
30+
onLoadMoreSpans?: () => void | Promise<void>;
31+
hasMoreSpans?: boolean;
32+
isLoadingMoreSpans?: boolean;
3033
}
3134

3235
export function RunTraceView({
@@ -44,6 +47,9 @@ export function RunTraceView({
4447
onCancelRun,
4548
onStreamClick,
4649
onSpanSelect,
50+
onLoadMoreSpans,
51+
hasMoreSpans,
52+
isLoadingMoreSpans,
4753
}: RunTraceViewProps) {
4854
if (error && !run) {
4955
return (
@@ -72,6 +78,9 @@ export function RunTraceView({
7278
onCancelRun={onCancelRun}
7379
onStreamClick={onStreamClick}
7480
onSpanSelect={onSpanSelect}
81+
onLoadMoreSpans={onLoadMoreSpans}
82+
hasMoreSpans={hasMoreSpans}
83+
isLoadingMoreSpans={isLoadingMoreSpans}
7584
/>
7685
</div>
7786
);

packages/web-shared/src/components/workflow-trace-view.tsx

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,9 @@ function TraceViewerWithContextMenu({
278278
onWakeUpSleep,
279279
onCancelRun,
280280
onResolveHook,
281+
onLoadMoreSpans,
282+
hasMoreSpans = false,
283+
isLoadingMoreSpans = false,
281284
children,
282285
}: {
283286
trace: { spans: Span[] };
@@ -294,9 +297,12 @@ function TraceViewerWithContextMenu({
294297
payload: unknown,
295298
hook?: Hook
296299
) => Promise<void>;
300+
onLoadMoreSpans?: () => void | Promise<void>;
301+
hasMoreSpans?: boolean;
302+
isLoadingMoreSpans?: boolean;
297303
children: ReactNode;
298304
}): ReactNode {
299-
const { dispatch } = useTraceViewer();
305+
const { state, dispatch } = useTraceViewer();
300306

301307
// Drive active span widths at 60fps without React re-renders
302308
useLiveTick(isLive);
@@ -413,6 +419,37 @@ function TraceViewerWithContextMenu({
413419
};
414420
}, [handleContextMenu]);
415421

422+
const loadingMoreRef = useRef(false);
423+
useEffect(() => {
424+
const timeline = state.timelineRef.current;
425+
if (!timeline || !onLoadMoreSpans || !hasMoreSpans) {
426+
return;
427+
}
428+
429+
const thresholdPx = 200;
430+
const maybeLoadMore = () => {
431+
if (loadingMoreRef.current || isLoadingMoreSpans || !hasMoreSpans) {
432+
return;
433+
}
434+
const remaining =
435+
timeline.scrollHeight - timeline.scrollTop - timeline.clientHeight;
436+
if (remaining > thresholdPx) {
437+
return;
438+
}
439+
440+
loadingMoreRef.current = true;
441+
Promise.resolve(onLoadMoreSpans()).finally(() => {
442+
loadingMoreRef.current = false;
443+
});
444+
};
445+
446+
timeline.addEventListener('scroll', maybeLoadMore);
447+
maybeLoadMore();
448+
return () => {
449+
timeline.removeEventListener('scroll', maybeLoadMore);
450+
};
451+
}, [state.timelineRef, onLoadMoreSpans, hasMoreSpans, isLoadingMoreSpans]);
452+
416453
const closeMenu = useCallback(() => {
417454
setContextMenu(null);
418455
}, []);
@@ -853,6 +890,9 @@ export const WorkflowTraceViewer = ({
853890
onStreamClick,
854891
onSpanSelect,
855892
onLoadEventData,
893+
onLoadMoreSpans,
894+
hasMoreSpans = false,
895+
isLoadingMoreSpans = false,
856896
}: {
857897
run: WorkflowRun;
858898
steps: Step[];
@@ -883,6 +923,12 @@ export const WorkflowTraceViewer = ({
883923
correlationId: string,
884924
eventId: string
885925
) => Promise<unknown | null>;
926+
/** Load next trace page when vertical scroll reaches bottom. */
927+
onLoadMoreSpans?: () => void | Promise<void>;
928+
/** Whether trace pagination has more data to load. */
929+
hasMoreSpans?: boolean;
930+
/** Whether trace pagination is currently fetching another page. */
931+
isLoadingMoreSpans?: boolean;
886932
}) => {
887933
const [selectedSpan, setSelectedSpan] = useState<SelectedSpanInfo | null>(
888934
null
@@ -1049,6 +1095,9 @@ export const WorkflowTraceViewer = ({
10491095
onWakeUpSleep={onWakeUpSleep}
10501096
onCancelRun={onCancelRun}
10511097
onResolveHook={onResolveHook}
1098+
onLoadMoreSpans={onLoadMoreSpans}
1099+
hasMoreSpans={hasMoreSpans}
1100+
isLoadingMoreSpans={isLoadingMoreSpans}
10521101
>
10531102
<TraceViewerTimeline
10541103
eagerRender

packages/web/app/components/run-detail-view.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,9 @@ export function RunDetailView({
357357
auxiliaryDataLoading,
358358
error,
359359
update,
360+
loadMoreTraceData,
361+
hasMoreTraceData,
362+
isLoadingMoreTraceData,
360363
} = useWorkflowTraceViewerData(env, runId, { live: true });
361364
const run = runData ?? ({} as WorkflowRun);
362365

@@ -723,6 +726,9 @@ export function RunDetailView({
723726
onWakeUpSleep={handleWakeUpSleep}
724727
onResolveHook={handleResolveHook}
725728
onLoadEventData={handleLoadSidebarEventData}
729+
onLoadMoreSpans={loadMoreTraceData}
730+
hasMoreSpans={hasMoreTraceData}
731+
isLoadingMoreSpans={isLoadingMoreTraceData}
726732
/>
727733
</div>
728734
</ErrorBoundary>

packages/web/app/lib/client/hooks/use-trace-viewer.test.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,13 @@ vi.mock('~/lib/rpc-client', () => ({
1111
fetchSteps: vi.fn(),
1212
fetchHooks: vi.fn(),
1313
fetchEvents: vi.fn(),
14+
fetchEventsByCorrelationId: vi.fn(),
1415
}));
1516

1617
import type { WorkflowRun } from '@workflow/world';
1718
import {
1819
fetchEvents,
20+
fetchEventsByCorrelationId,
1921
fetchHooks,
2022
fetchRun,
2123
fetchSteps,
@@ -50,6 +52,7 @@ function emptyPage() {
5052
describe('useWorkflowTraceViewerData', () => {
5153
beforeEach(() => {
5254
vi.clearAllMocks();
55+
vi.mocked(fetchEventsByCorrelationId).mockReturnValue(emptyPage());
5356
});
5457

5558
it('shows complete trace data on load', async () => {

0 commit comments

Comments
 (0)