Skip to content

Commit 4d7e946

Browse files
committed
Group suspended by rows by the same name
1 parent d7215b4 commit 4d7e946

File tree

1 file changed

+172
-15
lines changed

1 file changed

+172
-15
lines changed

packages/react-devtools-shared/src/devtools/views/Components/InspectedElementSuspendedBy.js

Lines changed: 172 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ type RowProps = {
4444
index: number,
4545
minTime: number,
4646
maxTime: number,
47+
skipName?: boolean,
4748
};
4849

4950
function 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+
334441
export 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

Comments
 (0)