From 71d71aa5c926ce563ce935ca97706ff0a57d89fc Mon Sep 17 00:00:00 2001 From: "mykola.gervasyuk" Date: Fri, 21 Aug 2026 17:06:05 +0300 Subject: [PATCH] perf: read the published run order by id instead of scanning it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The details dialog turns the order the list publishes into its own run array, and it did so by scanning that array: once per run to filter, then twice per comparison while sorting. On a build of ten thousand screenshots that is a hundred million comparisons for the filter alone, and the sort makes it far worse. It also runs whether the dialog is open or not, and on every list change — switching view, grouping variations, picking a tag filter — so the whole page stalled for over a second on each interaction. Measured in Chromium on a 10 000 run build: 1.4 s of the 1.9 s spent on a Group variations toggle was this one memo. Index the published order in a Map and read positions off it: open a build 1190 ms -> 544 ms switch to cards 755 ms -> 118 ms group variations 618 ms -> 100 ms The index of the run on screen is already memoised right below, so the render no longer scans for it a second time. --- src/components/TestDetailsDialog/index.tsx | 25 +++++++++++----------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/components/TestDetailsDialog/index.tsx b/src/components/TestDetailsDialog/index.tsx index 0f58fc6e..078a7d64 100644 --- a/src/components/TestDetailsDialog/index.tsx +++ b/src/components/TestDetailsDialog/index.tsx @@ -27,17 +27,20 @@ export const TestDetailsDialog: React.FunctionComponent = () => { const [notSavedChangesModal, setNotSavedChangesModal] = React.useState(false); const [navigationTargetId, setNavigationTargetId] = React.useState(); + // the published order is read by id, not scanned: a linear search per run — + // and two more per comparison while sorting — costs seconds on a build of + // thousands, and this runs on every list change, dialog open or not const testRuns = React.useMemo(() => { - if (filteredSortedTestRunIds) { - return allTestRuns - .filter((tr) => filteredSortedTestRunIds.includes(tr.id)) - .sort( - (a, b) => - filteredSortedTestRunIds.indexOf(a.id) - - filteredSortedTestRunIds.indexOf(b.id), - ); + if (!filteredSortedTestRunIds) { + return allTestRuns; } - return allTestRuns; + const positionById = new Map( + filteredSortedTestRunIds.map((id, position) => [id, position]), + ); + + return allTestRuns + .filter((testRun) => positionById.has(testRun.id)) + .sort((a, b) => positionById.get(a.id)! - positionById.get(b.id)!); }, [allTestRuns, filteredSortedTestRunIds]); const selectedTestRunIndex = React.useMemo( @@ -75,9 +78,7 @@ export const TestDetailsDialog: React.FunctionComponent = () => { item.id === selectedTestRun.id, - )} + currentRunIndex={selectedTestRunIndex} totalTestRunCount={testRuns.length} touched={touched} handleClose={() => navigateById()}