@@ -44,6 +44,7 @@ type RowProps = {
4444 index : number ,
4545 minTime : number ,
4646 maxTime : number ,
47+ skipName ?: boolean ,
4748} ;
4849
4950function getShortDescription ( name : string , description : string ) : string {
@@ -99,6 +100,7 @@ function SuspendedByRow({
99100 index,
100101 minTime,
101102 maxTime,
103+ skipName,
102104} : RowProps ) {
103105 const [ isOpen , setIsOpen ] = useState ( false ) ;
104106 const [ openIsPending , startOpenTransition ] = useTransition ( ) ;
@@ -166,8 +168,10 @@ function SuspendedByRow({
166168 className = { styles . CollapsableHeaderIcon }
167169 type = { isOpen ? 'expanded' : 'collapsed' }
168170 />
169- < span className = { styles . CollapsableHeaderTitle } > { name } </ span >
170- { shortDescription === '' ? null : (
171+ < span className = { styles . CollapsableHeaderTitle } >
172+ { skipName ? shortDescription : name }
173+ </ span >
174+ { skipName || shortDescription === '' ? null : (
171175 < >
172176 < span className = { styles . CollapsableHeaderSeparator } > { ' (' } </ span >
173177 < span className = { styles . CollapsableHeaderTitle } >
@@ -331,6 +335,109 @@ function compareTime(
331335 return ioA . start - ioB . start ;
332336}
333337
338+ type GroupProps = {
339+ bridge : FrontendBridge ,
340+ element : Element ,
341+ inspectedElement : InspectedElement ,
342+ store : Store ,
343+ name : string ,
344+ suspendedBy : Array < {
345+ index : number ,
346+ value : SerializedAsyncInfo ,
347+ } > ,
348+ minTime : number ,
349+ maxTime : number ,
350+ } ;
351+
352+ function SuspendedByGroup ( {
353+ bridge,
354+ element,
355+ inspectedElement,
356+ store,
357+ name,
358+ suspendedBy,
359+ minTime,
360+ maxTime,
361+ } : GroupProps ) {
362+ const [ isOpen , setIsOpen ] = useState ( false ) ;
363+ let start = Infinity ;
364+ let end = - Infinity ;
365+ let isRejected = false ;
366+ for ( let i = 0 ; i < suspendedBy . length ; i ++ ) {
367+ const asyncInfo : SerializedAsyncInfo = suspendedBy [ i ] . value ;
368+ const ioInfo = asyncInfo . awaited ;
369+ if ( ioInfo . start < start ) {
370+ start = ioInfo . start ;
371+ }
372+ if ( ioInfo . end > end ) {
373+ end = ioInfo . end ;
374+ }
375+ const value : any = ioInfo . value ;
376+ if (
377+ value !== null &&
378+ typeof value === 'object' &&
379+ value [ meta . name ] === 'rejected Thenable'
380+ ) {
381+ isRejected = true ;
382+ }
383+ }
384+ const timeScale = 100 / ( maxTime - minTime ) ;
385+ let left = ( start - minTime ) * timeScale ;
386+ let width = ( end - start ) * timeScale ;
387+ if ( width < 5 ) {
388+ // Use at least a 5% width to avoid showing too small indicators.
389+ width = 5 ;
390+ if ( left > 95 ) {
391+ left = 95 ;
392+ }
393+ }
394+ return (
395+ < div className = { styles . CollapsableRow } >
396+ < Button
397+ className = { styles . CollapsableHeader }
398+ onClick = { ( ) => {
399+ setIsOpen ( prevIsOpen => ! prevIsOpen ) ;
400+ } }
401+ title = { name } >
402+ < ButtonIcon
403+ className = { styles . CollapsableHeaderIcon }
404+ type = { isOpen ? 'expanded' : 'collapsed' }
405+ />
406+ < span className = { styles . CollapsableHeaderTitle } > { name } </ span >
407+ < div className = { styles . CollapsableHeaderFiller } />
408+ { isOpen ? null : (
409+ < div className = { styles . TimeBarContainer } >
410+ < div
411+ className = {
412+ ! isRejected ? styles . TimeBarSpan : styles . TimeBarSpanErrored
413+ }
414+ style = { {
415+ left : left . toFixed ( 2 ) + '%' ,
416+ width : width . toFixed ( 2 ) + '%' ,
417+ } }
418+ />
419+ </ div >
420+ ) }
421+ </ Button >
422+ { isOpen &&
423+ suspendedBy . map ( ( { value, index} ) => (
424+ < SuspendedByRow
425+ key = { index }
426+ index = { index }
427+ asyncInfo = { value }
428+ bridge = { bridge }
429+ element = { element }
430+ inspectedElement = { inspectedElement }
431+ store = { store }
432+ minTime = { minTime }
433+ maxTime = { maxTime }
434+ skipName = { true }
435+ />
436+ ) ) }
437+ </ div >
438+ ) ;
439+ }
440+
334441export default function InspectedElementSuspendedBy ( {
335442 bridge,
336443 element,
@@ -390,6 +497,27 @@ export default function InspectedElementSuspendedBy({
390497 suspendedBy === null ? [ ] : suspendedBy . map ( withIndex ) ;
391498 sortedSuspendedBy . sort ( compareTime ) ;
392499
500+ // Organize into groups of consecutive entries with the same name.
501+ const groups = [ ] ;
502+ let currentGroup = null ;
503+ let currentGroupName = null ;
504+ for ( let i = 0 ; i < sortedSuspendedBy . length ; i ++ ) {
505+ const entry = sortedSuspendedBy [ i ] ;
506+ const name = entry . value . awaited . name ;
507+ if (
508+ currentGroupName !== name ||
509+ ! name ||
510+ name === 'Promise' ||
511+ currentGroup === null
512+ ) {
513+ // Create a new group.
514+ currentGroupName = name ;
515+ currentGroup = [ ] ;
516+ groups . push ( currentGroup ) ;
517+ }
518+ currentGroup . push ( entry ) ;
519+ }
520+
393521 let unknownSuspenders = null ;
394522 switch ( inspectedElement . unknownSuspenders ) {
395523 case UNKNOWN_SUSPENDERS_REASON_PRODUCTION :
@@ -430,19 +558,48 @@ export default function InspectedElementSuspendedBy({
430558 < ButtonIcon type = "copy" />
431559 </ Button >
432560 </ div >
433- { sortedSuspendedBy . map ( ( { value, index} ) => (
434- < SuspendedByRow
435- key = { index }
436- index = { index }
437- asyncInfo = { value }
438- bridge = { bridge }
439- element = { element }
440- inspectedElement = { inspectedElement }
441- store = { store }
442- minTime = { minTime }
443- maxTime = { maxTime }
444- />
445- ) ) }
561+ { groups . length === 1
562+ ? // If it's only one type of suspender we can flatten it.
563+ groups [ 0 ] . map ( entry => (
564+ < SuspendedByRow
565+ key = { entry . index }
566+ index = { entry . index }
567+ asyncInfo = { entry . value }
568+ bridge = { bridge }
569+ element = { element }
570+ inspectedElement = { inspectedElement }
571+ store = { store }
572+ minTime = { minTime }
573+ maxTime = { maxTime }
574+ />
575+ ) )
576+ : groups . map ( ( entries , index ) =>
577+ entries . length === 1 ? (
578+ < SuspendedByRow
579+ key = { entries [ 0 ] . index }
580+ index = { entries [ 0 ] . index }
581+ asyncInfo = { entries [ 0 ] . value }
582+ bridge = { bridge }
583+ element = { element }
584+ inspectedElement = { inspectedElement }
585+ store = { store }
586+ minTime = { minTime }
587+ maxTime = { maxTime }
588+ />
589+ ) : (
590+ < SuspendedByGroup
591+ key = { entries [ 0 ] . index }
592+ name = { entries [ 0 ] . value . awaited . name }
593+ suspendedBy = { entries }
594+ bridge = { bridge }
595+ element = { element }
596+ inspectedElement = { inspectedElement }
597+ store = { store }
598+ minTime = { minTime }
599+ maxTime = { maxTime }
600+ />
601+ ) ,
602+ ) }
446603 { unknownSuspenders }
447604 </ div >
448605 ) ;
0 commit comments