@@ -67,10 +67,6 @@ function countParents(nodes: OtelSpanTree[]): Map<string, number> {
6767 return counts ;
6868}
6969
70- function hasInProgress ( nodes : OtelSpanTree [ ] ) : boolean {
71- return nodes . some ( ( n ) => n . inProgress || hasInProgress ( n . children ) ) ;
72- }
73-
7470// ---------------------------------------------------------------------------
7571// Synthetic span factories
7672// ---------------------------------------------------------------------------
@@ -96,7 +92,6 @@ function makeSyntheticRoot(
9692 createdAt : new Date ( earliestStart ) . toISOString ( ) ,
9793 spanAttributes : { [ ATTR . INSTRUMENTOR ] : 'hatchet' } ,
9894 children,
99- inProgress : children . some ( ( s ) => s . inProgress ) ,
10095 ...overrides ,
10196 } ;
10297}
@@ -312,30 +307,42 @@ function reparentOrphans(rootSpans: OtelSpanTree[]): void {
312307 const stepRunIndex = new Map < string , OtelSpanTree > ( ) ;
313308 buildStepRunIndex ( rootSpans , stepRunIndex ) ;
314309
315- const reparented = new Set < string > ( ) ;
310+ const ancestorIds = new Set < string > ( ) ;
316311
317- for ( const orphan of rootSpans ) {
318- if ( ! orphan . parentSpanId ) {
319- continue ;
320- }
321- const stepRunId = getStepRunId ( orphan ) ;
322- if ( ! stepRunId ) {
323- continue ;
312+ const reparentLevel = ( nodes : OtelSpanTree [ ] ) => {
313+ const reparented = new Set < string > ( ) ;
314+
315+ for ( const orphan of nodes ) {
316+ if ( ! orphan . parentSpanId ) {
317+ continue ;
318+ }
319+ const stepRunId = getStepRunId ( orphan ) ;
320+ if ( ! stepRunId ) {
321+ continue ;
322+ }
323+ const surrogate = stepRunIndex . get ( stepRunId ) ;
324+ if (
325+ surrogate &&
326+ surrogate . spanId !== orphan . spanId &&
327+ ! ancestorIds . has ( surrogate . spanId )
328+ ) {
329+ surrogate . children . push ( orphan ) ;
330+ reparented . add ( orphan . spanId ) ;
331+ }
324332 }
325- const surrogate = stepRunIndex . get ( stepRunId ) ;
326- if ( surrogate && surrogate . spanId !== orphan . spanId ) {
327- surrogate . children . push ( orphan ) ;
328- reparented . add ( orphan . spanId ) ;
333+
334+ if ( reparented . size > 0 ) {
335+ removeByPredicate ( nodes , ( n ) => reparented . has ( n . spanId ) ) ;
329336 }
330- }
331337
332- if ( reparented . size > 0 ) {
333- removeByPredicate ( rootSpans , ( n ) => reparented . has ( n . spanId ) ) ;
334- }
338+ for ( const node of nodes ) {
339+ ancestorIds . add ( node . spanId ) ;
340+ reparentLevel ( node . children ) ;
341+ ancestorIds . delete ( node . spanId ) ;
342+ }
343+ } ;
335344
336- for ( const node of rootSpans ) {
337- reparentOrphans ( node . children ) ;
338- }
345+ reparentLevel ( rootSpans ) ;
339346}
340347
341348function suppressOrphanedChildWorkflows (
@@ -484,12 +491,11 @@ function synthesizePendingTaskSpans(
484491// ---------------------------------------------------------------------------
485492
486493function sortChildrenStable ( nodes : OtelSpanTree [ ] ) : void {
487- const keys = new Map < OtelSpanTree , number > ( ) ;
488- for ( const node of nodes ) {
489- const src = node . queuedPhase ?? node ;
490- keys . set ( node , new Date ( src . createdAt ) . getTime ( ) ) ;
491- }
492- nodes . sort ( ( a , b ) => keys . get ( a ) ! - keys . get ( b ) ! ) ;
494+ nodes . sort ( ( a , b ) => {
495+ const aKey = ( a . queuedPhase ?? a ) . createdAt ;
496+ const bKey = ( b . queuedPhase ?? b ) . createdAt ;
497+ return aKey < bKey ? - 1 : aKey > bKey ? 1 : 0 ;
498+ } ) ;
493499
494500 for ( const node of nodes ) {
495501 if ( node . children . length > 1 ) {
@@ -542,11 +548,20 @@ function attachWorkflowQueuedPhase(
542548 } ;
543549}
544550
545- function computeHasErrorInSubtree ( node : OtelSpanTree ) : boolean {
546- const childHasError = node . children . some ( computeHasErrorInSubtree ) ;
551+ function computeSubtreeFlags ( node : OtelSpanTree ) : boolean {
552+ let childHasError = false ;
553+ let childHasInProgress = false ;
554+ for ( const child of node . children ) {
555+ if ( computeSubtreeFlags ( child ) ) {
556+ childHasInProgress = true ;
557+ }
558+ if ( child . hasErrorInSubtree ) {
559+ childHasError = true ;
560+ }
561+ }
547562 node . hasErrorInSubtree =
548563 node . statusCode === OtelStatusCode . ERROR || childHasError ;
549- return node . hasErrorInSubtree ;
564+ return node . inProgress === true || childHasInProgress ;
550565}
551566
552567// ---------------------------------------------------------------------------
@@ -606,7 +621,11 @@ function wrapMultipleRoots(
606621 if ( workflowRunTiming && rootSpans . length > 0 ) {
607622 attachWorkflowQueuedPhase ( rootSpans [ 0 ] , workflowRunTiming ) ;
608623 }
609- rootSpans . forEach ( computeHasErrorInSubtree ) ;
624+ for ( const root of rootSpans ) {
625+ if ( computeSubtreeFlags ( root ) ) {
626+ root . inProgress = true ;
627+ }
628+ }
610629 return rootSpans ;
611630 }
612631
@@ -638,14 +657,15 @@ function wrapMultipleRoots(
638657 [ ATTR . INSTRUMENTOR ] : 'hatchet' ,
639658 ...( workflowName && { [ ATTR . WORKFLOW_NAME ] : workflowName } ) ,
640659 } ,
641- inProgress : hasInProgress ( rootSpans ) ,
642660 } ) ;
643661
644662 if ( workflowRunTiming ) {
645663 attachWorkflowQueuedPhase ( syntheticRoot , workflowRunTiming ) ;
646664 }
647665
648- computeHasErrorInSubtree ( syntheticRoot ) ;
666+ if ( computeSubtreeFlags ( syntheticRoot ) ) {
667+ syntheticRoot . inProgress = true ;
668+ }
649669 return [ syntheticRoot ] ;
650670}
651671
@@ -670,7 +690,7 @@ function buildTreeFromTaskSummaries(
670690 } ) ;
671691 attachWorkflowQueuedPhase ( syntheticRoot , workflowRunTiming ) ;
672692 if ( syntheticRoot . queuedPhase ) {
673- computeHasErrorInSubtree ( syntheticRoot ) ;
693+ computeSubtreeFlags ( syntheticRoot ) ;
674694 return [ syntheticRoot ] ;
675695 }
676696 }
@@ -727,9 +747,5 @@ export const convertOtelSpansToOtelSpanTree = (
727747
728748 sortChildrenStable ( rootSpans ) ;
729749
730- if ( rootSpans . length === 1 && hasInProgress ( rootSpans [ 0 ] . children ) ) {
731- rootSpans [ 0 ] . inProgress = true ;
732- }
733-
734750 return wrapMultipleRoots ( rootSpans , workflowRunTiming ) ;
735751} ;
0 commit comments