diff --git a/src/__tests__/utils/setupTests.ts b/src/__tests__/utils/setupTests.ts index 2e642a626..0e3679ff5 100644 --- a/src/__tests__/utils/setupTests.ts +++ b/src/__tests__/utils/setupTests.ts @@ -16,6 +16,7 @@ import { Hooks } from 'taskcluster-client-web'; import { createStore } from '../../common/store'; import type { Store } from '../../common/store'; +import { clearTreeherderCaches } from '../../logic/treeherder'; import { fftkde } from '../../utils/kde.js'; let store: Store; @@ -98,6 +99,9 @@ afterEach(() => { // Also restore the fetch mock fetchMock.mockReset(); + // Clear the treeherder memoization caches so that tests aren't polluted. + clearTreeherderCaches(); + // Clear perfcompare cookies so they don't leak between tests. for (const cookie of document.cookie.split(';')) { const name = cookie.split('=')[0].trim(); diff --git a/src/components/CompareResults/loader.ts b/src/components/CompareResults/loader.ts index 353db7eec..bd7770cc6 100644 --- a/src/components/CompareResults/loader.ts +++ b/src/components/CompareResults/loader.ts @@ -5,8 +5,8 @@ import { MANN_WHITNEY_U, } from '../../common/constants'; import { - fetchCompareResults, fetchFakeCompareResults, + memoizedFetchCompareResults, memoizedFetchRevisionForRepository, } from '../../logic/treeherder'; import { @@ -147,7 +147,7 @@ async function fetchCompareResultsOnTreeherder({ testVersion?: TestVersion; }) { const promises = newRevs.map((newRev, i) => - fetchCompareResults({ + memoizedFetchCompareResults({ baseRev, baseRepo, newRev, diff --git a/src/components/CompareResults/overTimeLoader.ts b/src/components/CompareResults/overTimeLoader.ts index b47be0ea5..7f3911c67 100644 --- a/src/components/CompareResults/overTimeLoader.ts +++ b/src/components/CompareResults/overTimeLoader.ts @@ -6,7 +6,7 @@ import { MANN_WHITNEY_U, } from '../../common/constants'; import { - fetchCompareOverTimeResults, + memoizedFetchCompareOverTimeResults, memoizedFetchRevisionForRepository, } from '../../logic/treeherder'; import { @@ -147,7 +147,7 @@ async function fetchCompareOverTimeResultsOnTreeherder({ testVersion: TestVersion; }) { const promises = newRevs.map((newRev, i) => - fetchCompareOverTimeResults({ + memoizedFetchCompareOverTimeResults({ baseRepo, newRev, newRepo: newRepos[i], diff --git a/src/logic/treeherder.ts b/src/logic/treeherder.ts index b6921a9c2..e58f781ab 100644 --- a/src/logic/treeherder.ts +++ b/src/logic/treeherder.ts @@ -278,6 +278,53 @@ export const memoizedFetchRevisionForRepository = moize( { isPromise: true, isShallowEqual: true, maxSize: 5 }, ) as typeof fetchRevisionForRepository; +// Memoized versions of the compare results fetch functions. +// Going back and forth between the t-test and MWU views re-runs the route +// loader, which would refetch the results from Treeherder every time. +// We cache the value in memory which is keyed on the request parameters +const compareResultsCache = new Map>(); +export function memoizedFetchCompareResults( + params: FetchProps, +): Promise { + const normalizedParams = { + ...params, + testVersion: params.testVersion ?? STUDENT_T, + }; + const key = JSON.stringify(normalizedParams); + const cached = compareResultsCache.get(key); + if (cached) return cached; + const promise = fetchCompareResults(normalizedParams).catch((error) => { + compareResultsCache.delete(key); + throw error; + }); + compareResultsCache.set(key, promise); + return promise; +} + +const compareOverTimeResultsCache = new Map< + string, + Promise +>(); +export function memoizedFetchCompareOverTimeResults( + params: FetchOverTimeProps, +): Promise { + const normalizedParams = { + ...params, + testVersion: params.testVersion ?? STUDENT_T, + }; + const key = JSON.stringify(normalizedParams); + const cached = compareOverTimeResultsCache.get(key); + if (cached) return cached; + const promise = fetchCompareOverTimeResults(normalizedParams).catch( + (error) => { + compareOverTimeResultsCache.delete(key); + throw error; + }, + ); + compareOverTimeResultsCache.set(key, promise); + return promise; +} + // Memoized versions of the subtest fetch functions. // Each RevisionRow with has_subtests fires its own fetch, so memoization // prevents duplicate network calls when rows share the same signature IDs, @@ -294,7 +341,10 @@ export function memoizedFetchSubtestsCompareResults( const key = JSON.stringify(params); const cached = subtestCompareResultsCache.get(key); if (cached) return cached; - const promise = fetchSubtestsCompareResults(params); + const promise = fetchSubtestsCompareResults(params).catch((error) => { + subtestCompareResultsCache.delete(key); + throw error; + }); subtestCompareResultsCache.set(key, promise); return promise; } @@ -309,11 +359,23 @@ export function memoizedFetchSubtestsCompareOverTimeResults( const key = JSON.stringify(params); const cached = subtestCompareOverTimeResultsCache.get(key); if (cached) return cached; - const promise = fetchSubtestsCompareOverTimeResults(params); + const promise = fetchSubtestsCompareOverTimeResults(params).catch((error) => { + subtestCompareOverTimeResultsCache.delete(key); + throw error; + }); subtestCompareOverTimeResultsCache.set(key, promise); return promise; } +// Clears all the memoization caches in this file. Only tests should need +// this, to isolate the module-level caches between test cases. +export function clearTreeherderCaches() { + compareResultsCache.clear(); + compareOverTimeResultsCache.clear(); + subtestCompareResultsCache.clear(); + subtestCompareOverTimeResultsCache.clear(); +} + export async function fetchJobInformationFromJobId( repo: string, jobId: number,