Repository: mozilla/perfcompare
Pull request #879: Bug 1962486 - Optimize and create new perfcompare loader
Author: login: Andrej1198
Labels: name: Changes Requested
Created: 2025-04-25T21:02:20Z | Merged: 2025-05-08T20:33:43Z

What we are doing:
- 1) We are creating a new loader for perfcompare
- 2) Accomodating the updated tocommit api on treeherder to get back results faster

Why:
- 1) To leave the main loader alone and to create one specific to ./mach try perf
- 2) Treeherder needs extra information to get results faster, we are passing those results along to it 


Diff:
diff --git a/src/__tests__/Search/fetchRevisionFromHash.test.tsx b/src/__tests__/Search/fetchRevisionFromHash.test.tsx
index 97b309734..9c2bc3887 100644
--- a/src/__tests__/Search/fetchRevisionFromHash.test.tsx
+++ b/src/__tests__/Search/fetchRevisionFromHash.test.tsx
@@ -2,6 +2,36 @@ import App, { router } from '../../components/App';
 import { FetchMockSandbox, render } from '../utils/test-utils';
 
 describe('Hash to commit good run validating', () => {
+  it('Should return a baseRev not found if we return a null baseRev', async () => {
+    // Silence console.error for a better console output. We'll check its result later.
+    // If an expectation toHaveBeenCalledTimes fails, it might be easier to
+    // remove the mockImplementation part to debug.
+
+    jest.spyOn(console, 'error').mockImplementation(() => {});
+    (global.fetch as FetchMockSandbox).get(
+      'glob:https://treeherder.mozilla.org/api/project/*/hash/*',
+      (url) => {
+        return url.includes('tocommit')
+          ? {
+              baseRevision: null,
+              newRevision: 'Another',
+            }
+          : {};
+      },
+    );
+
+    // Error 1: no baseRev
+    await router.navigate(
+      '/compare-hash-results?baseHash=7844063918536384434&baseRepo=try&newHash=83782697878z014813466&newHashDate=03-20-2025&baseHashDate=03-20-2025&newRepo=try&framework=1',
+    );
+    render(<App />);
+    expect(console.error).toHaveBeenCalledWith(
+      new Error('The parameter baseRev is missing.'),
+    );
+    expect(console.error).toHaveBeenCalledTimes(1);
+    (console.error as jest.Mock).mockClear();
+  });
+
   it('should reject hashes not associated with any commits', async () => {
     jest.spyOn(console, 'error').mockImplementation(() => {});
 
@@ -18,7 +48,7 @@ describe('Hash to commit good run validating', () => {
     );
 
     await router.navigate(
-      '/compare-results?baseHash=7844063918536384434&baseRepo=try&newHash=83782697878014813466&newRepo=try&framework=1',
+      '/compare-hash-results?baseHash=7844063918536384434&baseRepo=try&newHash=83782697878z014813466&newHashDate=03-20-2025&baseHashDate=03-20-2025&newRepo=try&framework=1',
     );
     render(<App />);
     // Expecting to have a hashes do not correlate with revision error
@@ -31,29 +61,46 @@ describe('Hash to commit good run validating', () => {
     (console.error as jest.Mock).mockClear();
   });
 
-  it('should reject invalid results returned from treeherder', async () => {
+  it('should display a different error message if the datehash is today and not found', async () => {
     jest.spyOn(console, 'error').mockImplementation(() => {});
 
     (global.fetch as FetchMockSandbox).get(
       'glob:https://treeherder.mozilla.org/api/project/*/hash/*',
-      (url) => {
-        return url.includes('tocommit')
+      (url) =>
+        url.includes('tocommit')
           ? {
-              badbaseRevision: 'Press',
-              newRevision: 'Another',
+              throws: new Error(
+                'Error when requesting treeherder: ["83782697878014813466 or 7844063918536384434 do not correspond to any existing hashes please double check both hashes you provided"]',
+              ),
             }
-          : {};
-      },
+          : {},
     );
 
     await router.navigate(
-      '/compare-results?baseHash=7844063918536384434&baseRepo=try&newHash=83782697878014813466&newRepo=try&framework=1',
+      '/compare-hash-results?baseHash=7844063918536384434&baseRepo=try&newHash=83782697878z014813466&newHashDate=' +
+        new Date().toISOString().split('T')[0] +
+        '&baseHashDate=03-20-2025&newRepo=try&framework=1',
+    );
+    render(<App />);
+    // Expecting to have a hashes do not correlate with revision error
+    expect(console.error).toHaveBeenCalledWith(
+      new Error('Results pushed today, still processing...'),
+    );
+    expect(console.error).toHaveBeenCalledTimes(1);
+    (console.error as jest.Mock).mockClear();
+  });
+
+  it('should reject invalid results returned from treeherder', async () => {
+    jest.spyOn(console, 'error').mockImplementation(() => {});
+
+    await router.navigate(
+      '/compare-hash-results?baseHash=7844063918536384434&baseRepo=try&newHash=83782697878014813466&newRepo=try&framework=1',
     );
     render(<App />);
     // Expecting to have an error about unable to parse baseRev and/or newRev returned from treeherder
     expect(console.error).toHaveBeenCalledWith(
       new Error(
-        'Unable to parse baseRev and/or newRev returned from treeherder',
+        'Not all values were supplied please check you provided all 4 of: baseHash, baseHashDate, newHash, newHashDate',
       ),
     );
     expect(console.error).toHaveBeenCalledTimes(1);
diff --git a/src/components/App.tsx b/src/components/App.tsx
index 0dc18bb30..427c38902 100644
--- a/src/components/App.tsx
+++ b/src/components/App.tsx
@@ -16,6 +16,7 @@ import { useAppSelector } from '../hooks/app';
 import { Strings } from '../resources/Strings';
 import { Banner } from '../styles/Banner';
 import getProtocolTheme from '../theme/protocolTheme';
+import { loader as hashToCommitLoader } from './CompareResults/hashToCommitLoader';
 import { loader as compareLoader } from './CompareResults/loader';
 import { loader as compareOverTimeLoader } from './CompareResults/overTimeLoader';
 import OverTimeResultsView from './CompareResults/OverTimeResultsView';
@@ -81,6 +82,13 @@ export const router = createBrowserRouter(
         errorElement={<PageError title={Strings.metaData.pageTitle.results} />}
       />
 
+      <Route
+        path='/compare-hash-results'
+        loader={hashToCommitLoader}
+        element={<ResultsView title={Strings.metaData.pageTitle.results} />}
+        errorElement={<PageError title={Strings.metaData.pageTitle.results} />}
+      />
+
       <Route
         path='/compare-over-time-results'
         loader={compareOverTimeLoader}
diff --git a/src/components/CompareResults/ResultsView.tsx b/src/components/CompareResults/ResultsView.tsx
index 97181e363..011dc206c 100644
--- a/src/components/CompareResults/ResultsView.tsx
+++ b/src/components/CompareResults/ResultsView.tsx
@@ -4,6 +4,7 @@ import Grid from '@mui/material/Grid';
 import { useLoaderData } from 'react-router-dom';
 import { style } from 'typestyle';
 
+import type { HashLoaderReturnValue } from './hashToCommitLoader';
 import type { LoaderReturnValue } from './loader';
 import ResultsMain from './ResultsMain';
 import { useAppSelector } from '../../hooks/app';
@@ -17,7 +18,7 @@ interface ResultsViewProps {
 }
 function ResultsView(props: ResultsViewProps) {
   const { baseRevInfo, newRevsInfo, frameworkId, baseRepo, newRepos } =
-    useLoaderData() as LoaderReturnValue;
+    useLoaderData() as LoaderReturnValue | HashLoaderReturnValue;
 
   const newRepo = newRepos[0];
   const { title } = props;
diff --git a/src/components/CompareResults/hashToCommitLoader.ts b/src/components/CompareResults/hashToCommitLoader.ts
new file mode 100644
index 000000000..4fca9f80a
--- /dev/null
+++ b/src/components/CompareResults/hashToCommitLoader.ts
@@ -0,0 +1,96 @@
+import { checkValues, getComparisonInformation } from './loader';
+import { compareView } from '../../common/constants';
+import { fetchRevisionFromHash } from '../../logic/treeherder';
+import { Changeset, CompareResultsItem, Repository } from '../../types/state';
+import { Framework } from '../../types/types';
+
+// This function is responsible for fetching the data from the URL. It's called
+// by React Router DOM when the compare-hash-results path is requested.
+// This loader is the only one used by ./mach try perf, and due to recent
+// changes mach try perf we can't get instant push links to try when we push.
+// We attach a hash to the commit message and search for that hash, and return
+// the commit associated with that hash and update the baseRev and newRev
+export async function loader({ request }: { request: Request }) {
+  const url = new URL(request.url);
+  const baseHashFromUrl = url.searchParams.get('baseHash');
+  const baseHashDateFromUrl = url.searchParams.get('baseHashDate');
+  const newHashFromUrl = url.searchParams.get('newHash');
+  const newHashDateFromUrl = url.searchParams.get('newHashDate') as string;
+  const baseRepoFromUrl = url.searchParams.get('baseRepo') as
+    | Repository['name']
+    | null;
+  const newReposFromUrl = url.searchParams.getAll(
+    'newRepo',
+  ) as Repository['name'][];
+  const frameworkFromUrl = url.searchParams.get('framework');
+  const pushed_today =
+    new Date(newHashDateFromUrl).toISOString().split('T')[0] ==
+    new Date().toISOString().split('T')[0];
+  let commits_from_hashes: CommitFromHashData = {
+    baseRevision: '',
+    newRevision: '',
+  };
+  if (
+    !baseHashFromUrl ||
+    !baseHashDateFromUrl ||
+    !newHashFromUrl ||
+    !newHashDateFromUrl
+  ) {
+    throw new Error(
+      'Not all values were supplied please check you provided all 4 of: baseHash, baseHashDate, newHash, newHashDate',
+    );
+  }
+  try {
+    commits_from_hashes = await fetchRevisionFromHash(
+      baseHashFromUrl,
+      baseHashDateFromUrl,
+      newHashFromUrl,
+      newHashDateFromUrl,
+      'try',
+    );
+  } catch (e) {
+    if (pushed_today) {
+      throw new Error('Results pushed today, still processing...');
+    } else {
+      throw e;
+    }
+  }
+  const baseRevsFromHash = commits_from_hashes.baseRevision;
+  const newRevsFromHash = [commits_from_hashes.newRevision];
+  const { baseRev, baseRepo, newRevs, newRepos, frameworkId, frameworkName } =
+    checkValues({
+      baseRev: baseRevsFromHash,
+      baseRepo: baseRepoFromUrl,
+      newRevs: newRevsFromHash,
+      newRepos: newReposFromUrl,
+      framework: frameworkFromUrl,
+    });
+  return await getComparisonInformation(
+    baseRev,
+    baseRepo,
+    newRevs,
+    newRepos,
+    frameworkId,
+    frameworkName,
+  );
+}
+
+type HashLoaderData = {
+  results: Promise<CompareResultsItem[][]>;
+  baseRev: string;
+  baseRevInfo: Changeset;
+  baseRepo: Repository['name'];
+  newRevs: string[];
+  newRevsInfo: Changeset[];
+  newRepos: Repository['name'][];
+  frameworkId: Framework['id'];
+  frameworkName: Framework['name'];
+  view: typeof compareView;
+  generation: number;
+};
+
+type CommitFromHashData = {
+  baseRevision: string;
+  newRevision: string;
+};
+export type HashLoaderReturnValue = HashLoaderData;
diff --git a/src/components/CompareResults/loader.ts b/src/components/CompareResults/loader.ts
index ad15ae92c..c618e66be 100644
--- a/src/components/CompareResults/loader.ts
+++ b/src/components/CompareResults/loader.ts
@@ -4,14 +4,13 @@ import {
   fetchCompareResults,
   fetchFakeCompareResults,
   memoizedFetchRevisionForRepository,
-  fetchRevisionFromHash,
 } from '../../logic/treeherder';
 import { Changeset, CompareResultsItem, Repository } from '../../types/state';
 import { FakeCommitHash, Framework } from '../../types/types';
 
 // This function checks and sanitizes the input values, then returns values that
 // we can then use in the rest of the application.
-function checkValues({
+export function checkValues({
   baseRev,
   baseRepo,
   newRevs,
@@ -180,60 +179,44 @@ export async function loader({ request }: { request: Request }) {
       generation: generationCounter++,
     };
   }
-  const baseRevFromUrl = url.searchParams.get('baseRev');
-  const newRevsFromUrl = url.searchParams.getAll('newRev');
-  const baseHashFromUrl = url.searchParams.get('baseHash');
-  const newHashFromUrl = url.searchParams.get('newHash');
-
-  // This loader is the only one used by ./mach try perf, and due to
-  // recent changes mach try perf can't get instant push links to try
-  // when we push, we must attach a hash to the commit message and
-  // search commits for that hash, and return the commit associated
-  // with that hash and update the baseRev and newRev
-  let baseRevsFromHash = baseRevFromUrl;
-  let newRevsFromHash = newRevsFromUrl;
-  let notUsingHashToFindRevisions = true;
-  if (baseHashFromUrl && newHashFromUrl) {
-    // Update to the boolean to indicate we are using a hash to find revisions
-    notUsingHashToFindRevisions = false;
-    const commits_from_hashes = await fetchRevisionFromHash(
-      baseHashFromUrl,
-      newHashFromUrl,
-      'try',
-    );
-    baseRevsFromHash = commits_from_hashes.baseRevision;
-    newRevsFromHash = [commits_from_hashes.newRevision];
-    if (baseRevsFromHash == undefined || newRevsFromHash[0] == undefined) {
-      throw new Error(
-        'Unable to parse baseRev and/or newRev returned from treeherder',
-      );
-    }
-  }
 
+  const baseRevFromUrl = url.searchParams.get('baseRev');
   const baseRepoFromUrl = url.searchParams.get('baseRepo') as
     | Repository['name']
     | null;
+  const newRevsFromUrl = url.searchParams.getAll('newRev');
   const newReposFromUrl = url.searchParams.getAll(
     'newRepo',
   ) as Repository['name'][];
   const frameworkFromUrl = url.searchParams.get('framework');
-  // Update baseRev and newRev if and only if we are supplied with newHash and baseHash
-  const updateBaseRev = notUsingHashToFindRevisions
-    ? baseRevFromUrl
-    : baseRevsFromHash;
-  const updateNewRev = notUsingHashToFindRevisions
-    ? newRevsFromUrl
-    : newRevsFromHash;
 
   const { baseRev, baseRepo, newRevs, newRepos, frameworkId, frameworkName } =
     checkValues({
-      baseRev: updateBaseRev,
+      baseRev: baseRevFromUrl,
       baseRepo: baseRepoFromUrl,
-      newRevs: updateNewRev,
+      newRevs: newRevsFromUrl,
       newRepos: newReposFromUrl,
       framework: frameworkFromUrl,
     });
 
+  return await getComparisonInformation(
+    baseRev,
+    baseRepo,
+    newRevs,
+    newRepos,
+    frameworkId,
+    frameworkName,
+  );
+}
+
+export async function getComparisonInformation(
+  baseRev: string,
+  baseRepo: Repository['name'],
+  newRevs: string[],
+  newRepos: Repository['name'][],
+  frameworkId: Framework['id'],
+  frameworkName: Framework['name'],
+) {
   const resultsPromise = fetchCompareResultsOnTreeherder({
     baseRev,
     baseRepo,
diff --git a/src/logic/treeherder.ts b/src/logic/treeherder.ts
index 40ce1da70..1e5ae3378 100644
--- a/src/logic/treeherder.ts
+++ b/src/logic/treeherder.ts
@@ -49,6 +49,24 @@ type FetchSubtestsOverTimeProps = {
   newParentSignature: string;
 };
 
+export async function fetchRevisionFromHash(
+  basehash: string,
+  basehashdate: string,
+  newhash: string,
+  newhashdate: string,
+  repo: string,
+) {
+  const searchParams = new URLSearchParams({
+    basehash: basehash,
+    newhash: newhash,
+    basehashdate: basehashdate,
+    newhashdate: newhashdate,
+  });
+  const url = `${treeherderBaseURL}/api/project/${repo}/hash/tocommit/?${searchParams.toString()}`;
+  const response = await fetchFromTreeherder(url);
+  return response.json() as Promise<CommitToHash>;
+}
+
 async function fetchFromTreeherder(url: string) {
   const response = await fetch(url);
   if (!response.ok) {
@@ -64,21 +82,7 @@ async function fetchFromTreeherder(url: string) {
   }
   return response;
 }
-
-// This fetches the revision with the hash inside the ./mach try perf pushed job
-export async function fetchRevisionFromHash(
-  basehash: string,
-  newhash: string,
-  repo: string,
-) {
-  const searchParams = new URLSearchParams({
-    basehash: basehash,
-    newhash: newhash,
-  });
-  const url = `${treeherderBaseURL}/api/project/${repo}/hash/tocommit/?${searchParams.toString()}`;
-  const response = await fetchFromTreeherder(url);
-  return response.json() as Promise<CommitToHash>;
-}
+// This fetches the based on the hash inside the commit message of ./mach try perf pushed jobs
 
 // This fetches data from the Treeherder API /api/perfcompare/results.
 // This API returns the results of a comparison between 2 revisions.


Reviews:
user: login: kala-moz
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-28T14:04:01Z
user: login: kala-moz
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-30T20:27:12Z
user: login: kala-moz
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-30T20:27:52Z
user: login: julienw
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-05T12:56:58Z
user: login: julienw
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-05T12:57:52Z
user: login: Andrej1198
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-05T17:56:22Z
user: login: Andrej1198
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-05T17:58:24Z
user: login: kala-moz
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-05T21:49:13Z
user: login: julienw
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-06T09:13:35Z
user: login: Andrej1198
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-06T14:38:52Z
user: login: Andrej1198
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-07T15:33:57Z
user: login: kala-moz
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-07T22:52:37Z
user: login: kala-moz
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-07T22:53:39Z
user: login: kala-moz
body: LGTM, Andrej! Thanks for the good work. I only have a nit about updating the comments in your `hashToCommitLoader.ts`
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-07T22:56:48Z
user: login: kala-moz
body: Thanks for the updates!
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-08T19:49:25Z

Review comments:
diff_hunk: @@ -81,6 +82,13 @@ export const router = createBrowserRouter(
         errorElement={<PageError title={Strings.metaData.pageTitle.results} />}
       />
 
+        <Route
+        path='/compare-hash-results'
+        loader={hashToCommitLoader}
+        element={<ResultsView title={Strings.metaData.pageTitle.results} />}
path: src/components/App.tsx
user: login: kala-moz
body: @Andrej1198 You'll want to use your `HashResultsView` here not `ResultsView`. 
created_at: 2025-04-28T14:04:00Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,66 @@
+import { useEffect } from 'react';
+
+import Grid from '@mui/material/Grid';
+import { useLoaderData } from 'react-router-dom';
+import { style } from 'typestyle';
+
+import type { LoaderReturnValue } from './loader';
path: src/components/CompareResults/HashResultsView.tsx
user: login: kala-moz
body: @Andrej1198 make sure to import your loader here `hashToCommitLoader`.
created_at: 2025-04-30T20:27:11Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,66 @@
+import { useEffect } from 'react';
+
+import Grid from '@mui/material/Grid';
+import { useLoaderData } from 'react-router-dom';
+import { style } from 'typestyle';
+
+import type { LoaderReturnValue } from './loader';
path: src/components/CompareResults/HashResultsView.tsx
user: login: kala-moz
body: Not sure if your unique loader is still WIP but just a reminder. 
created_at: 2025-04-30T20:27:52Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,66 @@
+import { useEffect } from 'react';
+
+import Grid from '@mui/material/Grid';
+import { useLoaderData } from 'react-router-dom';
+import { style } from 'typestyle';
+
+import type { LoaderReturnValue } from './loader';
+import ResultsMain from './ResultsMain';
+import { useAppSelector } from '../../hooks/app';
+import { SearchContainerStyles, background } from '../../styles';
+import CompareWithBase from '../Search/CompareWithBase';
+import { LinkToHome } from '../Shared/LinkToHome';
+import PerfCompareHeader from '../Shared/PerfCompareHeader';
+
+interface ResultsViewProps {
+  title: string;
+}
+function ResultsView(props: ResultsViewProps) {
path: src/components/CompareResults/HashResultsView.tsx
user: login: julienw
body: hey, can you please explain how this component is different to the normal ResultsView?

I was initially thinking we could just reuse the same component, only the loader providing the information would be different.
created_at: 2025-05-05T12:56:57Z
author_association: CONTRIBUTOR
diff_hunk: @@ -18,42 +18,13 @@ describe('Hash to commit good run validating', () => {
     );
 
     await router.navigate(
-      '/compare-results?baseHash=7844063918536384434&baseRepo=try&newHash=83782697878014813466&newRepo=try&framework=1',
+      '/compare-hash-results?baseHash=7844063918536384434&baseRepo=try&newHash=83782697878014813466&newRepo=try&framework=1',
     );
     render(<App />);
     // Expecting to have a hashes do not correlate with revision error
     expect(console.error).toHaveBeenCalledWith(
       new Error(
-        'Error when requesting treeherder: ["83782697878014813466 or 7844063918536384434 do not correspond to any existing hashes please double check both hashes you provided"]',
-      ),
-    );
-    expect(console.error).toHaveBeenCalledTimes(1);
-    (console.error as jest.Mock).mockClear();
-  });
-
-  it('should reject invalid results returned from treeherder', async () => {
path: src/__tests__/Search/fetchRevisionFromHash.test.tsx
user: login: julienw
body: We don't want this test anymore?
created_at: 2025-05-05T12:57:51Z
author_association: CONTRIBUTOR
diff_hunk: @@ -18,42 +18,13 @@ describe('Hash to commit good run validating', () => {
     );
 
     await router.navigate(
-      '/compare-results?baseHash=7844063918536384434&baseRepo=try&newHash=83782697878014813466&newRepo=try&framework=1',
+      '/compare-hash-results?baseHash=7844063918536384434&baseRepo=try&newHash=83782697878014813466&newRepo=try&framework=1',
     );
     render(<App />);
     // Expecting to have a hashes do not correlate with revision error
     expect(console.error).toHaveBeenCalledWith(
       new Error(
-        'Error when requesting treeherder: ["83782697878014813466 or 7844063918536384434 do not correspond to any existing hashes please double check both hashes you provided"]',
-      ),
-    );
-    expect(console.error).toHaveBeenCalledTimes(1);
-    (console.error as jest.Mock).mockClear();
-  });
-
-  it('should reject invalid results returned from treeherder', async () => {
path: src/__tests__/Search/fetchRevisionFromHash.test.tsx
user: login: Andrej1198
body: I do this was a WIP but I forgot to add the tag, sorry
created_at: 2025-05-05T17:56:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,66 @@
+import { useEffect } from 'react';
+
+import Grid from '@mui/material/Grid';
+import { useLoaderData } from 'react-router-dom';
+import { style } from 'typestyle';
+
+import type { LoaderReturnValue } from './loader';
+import ResultsMain from './ResultsMain';
+import { useAppSelector } from '../../hooks/app';
+import { SearchContainerStyles, background } from '../../styles';
+import CompareWithBase from '../Search/CompareWithBase';
+import { LinkToHome } from '../Shared/LinkToHome';
+import PerfCompareHeader from '../Shared/PerfCompareHeader';
+
+interface ResultsViewProps {
+  title: string;
+}
+function ResultsView(props: ResultsViewProps) {
path: src/components/CompareResults/HashResultsView.tsx
user: login: Andrej1198
body: I spoke with Carla and she told me I needed to add 4 things for a new route:
1) A route in App.tsx
2) A loader
3) A view
4) A test

In my mind I created them all but I noticed, as you did that the view is the exact same and as I also discovered I can just re-use the view instead of duplicating as much as I did, my confusion and novice approach led me to duplicate and eventually I realized I can just re-use instead of duplicating
created_at: 2025-05-05T17:58:23Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,66 @@
+import { useEffect } from 'react';
+
+import Grid from '@mui/material/Grid';
+import { useLoaderData } from 'react-router-dom';
+import { style } from 'typestyle';
+
+import type { LoaderReturnValue } from './loader';
+import ResultsMain from './ResultsMain';
+import { useAppSelector } from '../../hooks/app';
+import { SearchContainerStyles, background } from '../../styles';
+import CompareWithBase from '../Search/CompareWithBase';
+import { LinkToHome } from '../Shared/LinkToHome';
+import PerfCompareHeader from '../Shared/PerfCompareHeader';
+
+interface ResultsViewProps {
+  title: string;
+}
+function ResultsView(props: ResultsViewProps) {
path: src/components/CompareResults/HashResultsView.tsx
user: login: kala-moz
body: @julienw @Andrej1198 My bad, Andre.  I initially recommended a separate view because I thought `ResultsView` was importing the loader values from `./loader` not `./hashToCommitLoader`. I had been confused on how the imported loader values work via the unique views. I was caught off guard from a quick look at`ResultsView` getting the type from `./loader`. The view knows which loader to use based on the loader set in `App.jsx.`  Maybe in the future we can create _a type for the loaders in the types file_ vs from one specific loader. But that's more of a nit. 


created_at: 2025-05-05T21:49:13Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,66 @@
+import { useEffect } from 'react';
+
+import Grid from '@mui/material/Grid';
+import { useLoaderData } from 'react-router-dom';
+import { style } from 'typestyle';
+
+import type { LoaderReturnValue } from './loader';
+import ResultsMain from './ResultsMain';
+import { useAppSelector } from '../../hooks/app';
+import { SearchContainerStyles, background } from '../../styles';
+import CompareWithBase from '../Search/CompareWithBase';
+import { LinkToHome } from '../Shared/LinkToHome';
+import PerfCompareHeader from '../Shared/PerfCompareHeader';
+
+interface ResultsViewProps {
+  title: string;
+}
+function ResultsView(props: ResultsViewProps) {
path: src/components/CompareResults/HashResultsView.tsx
user: login: julienw
body: Yeah, the typing situation isn't perfect.

Here is another example where we used 2 different loaders for the same view: https://github.com/mozilla/perfcompare/blob/ea276e9b317bb1d95e5e5971cefc7e9303fbd376/src/components/CompareResults/SubtestsResults/SubtestsResultsMain.tsx#L34-L36

See how we import both loader types and use them here, so that we don't lie to the type system and prevent future issues (if we change the return type of one loader but not the other one).
created_at: 2025-05-06T09:13:35Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,66 @@
+import { useEffect } from 'react';
+
+import Grid from '@mui/material/Grid';
+import { useLoaderData } from 'react-router-dom';
+import { style } from 'typestyle';
+
+import type { LoaderReturnValue } from './loader';
+import ResultsMain from './ResultsMain';
+import { useAppSelector } from '../../hooks/app';
+import { SearchContainerStyles, background } from '../../styles';
+import CompareWithBase from '../Search/CompareWithBase';
+import { LinkToHome } from '../Shared/LinkToHome';
+import PerfCompareHeader from '../Shared/PerfCompareHeader';
+
+interface ResultsViewProps {
+  title: string;
+}
+function ResultsView(props: ResultsViewProps) {
path: src/components/CompareResults/HashResultsView.tsx
user: login: Andrej1198
body: @Carla-Moz not at all something to apologize for, I mis-understood your correct advice and appreciate your patience and your in-depth explaination and nit advice of what I should have done :)
created_at: 2025-05-06T14:38:52Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,66 @@
+import { useEffect } from 'react';
+
+import Grid from '@mui/material/Grid';
+import { useLoaderData } from 'react-router-dom';
+import { style } from 'typestyle';
+
+import type { LoaderReturnValue } from './loader';
path: src/components/CompareResults/HashResultsView.tsx
user: login: Andrej1198
body: No longer using HashResultsView, closing this conversation
created_at: 2025-05-07T15:33:57Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,57 @@
+import { checkValues, getComparisonInformation } from './loader';
+import { fetchRevisionFromHash } from '../../logic/treeherder';
+import { Repository } from '../../types/state';
+
+// This function is responsible for fetching the data from the URL. It's called
+// by React Router DOM when the compare-results path is requested.
path: src/components/CompareResults/hashToCommitLoader.ts
user: login: kala-moz
body: Small nit, but you may want to update these comments here for the right context.
created_at: 2025-05-07T22:52:37Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,57 @@
+import { checkValues, getComparisonInformation } from './loader';
+import { fetchRevisionFromHash } from '../../logic/treeherder';
+import { Repository } from '../../types/state';
+
+// This function is responsible for fetching the data from the URL. It's called
+// by React Router DOM when the compare-results path is requested.
path: src/components/CompareResults/hashToCommitLoader.ts
user: login: kala-moz
body: You can use the old comments you had from the `loader` to explain why we need this `hashToCommitLoader.` Also can add extras details about why you've included the dates too. 
created_at: 2025-05-07T22:53:39Z
author_association: CONTRIBUTOR

PR comments:
user: login: netlify[bot]
created_at: 2025-04-25T21:02:37Z
body: ### <span aria-hidden="true">✅</span> Deploy Preview for *mozilla-perfcompare* ready!


|  Name | Link |
|:-:|------------------------|
|<span aria-hidden="true">🔨</span> Latest commit | b412601cb6057f50d62183f87149f510c941cda6 |
|<span aria-hidden="true">🔍</span> Latest deploy log | https://app.netlify.com/sites/mozilla-perfcompare/deploys/681d148d237f46000838c4b2 |
|<span aria-hidden="true">😎</span> Deploy Preview | [https://deploy-preview-879--mozilla-perfcompare.netlify.app](https://deploy-preview-879--mozilla-perfcompare.netlify.app) |
|<span aria-hidden="true">📱</span> Preview on mobile | <details><summary> Toggle QR Code... </summary><br /><br />![QR Code](https://app.netlify.com/qr-code/eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1cmwiOiJodHRwczovL2RlcGxveS1wcmV2aWV3LTg3OS0tbW96aWxsYS1wZXJmY29tcGFyZS5uZXRsaWZ5LmFwcCJ9.NA7JQDOrzkkr9KCtHnw5HjZJ8SwicqInIyxf53OM7Ao)<br /><br />_Use your smartphone camera to open QR code link._</details> |
---
<!-- [mozilla-perfcompare Preview](https://deploy-preview-879--mozilla-perfcompare.netlify.app) -->
_To edit notification comments on pull requests, go to your [Netlify site configuration](https://app.netlify.com/sites/mozilla-perfcompare/configuration/notifications#deploy-webhooks)._
author_association: NONE
user: login: Andrej1198
created_at: 2025-04-25T21:03:45Z
body: I have not written unit tests yet, and I need to clean up the rest of the codebase FYI I am not expecting a review I just got the route working locally loading results
author_association: CONTRIBUTOR
user: login: codecov[bot]
created_at: 2025-05-05T18:04:58Z
body: ## [Codecov](https://app.codecov.io/gh/mozilla/perfcompare/pull/879?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=mozilla) Report
Attention: Patch coverage is `95.34884%` with `2 lines` in your changes missing coverage. Please review.
> Project coverage is 94.14%. Comparing base [(`3566259`)](https://app.codecov.io/gh/mozilla/perfcompare/commit/3566259b5c9e38245cdbc82b59528c32715865e2?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=mozilla) to head [(`b412601`)](https://app.codecov.io/gh/mozilla/perfcompare/commit/b412601cb6057f50d62183f87149f510c941cda6?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=mozilla).
> Report is 1 commits behind head on main.

| [Files with missing lines](https://app.codecov.io/gh/mozilla/perfcompare/pull/879?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=mozilla) | Patch % | Lines |
|---|---|---|
| [...rc/components/CompareResults/hashToCommitLoader.ts](https://app.codecov.io/gh/mozilla/perfcompare/pull/879?src=pr&el=tree&filepath=src%2Fcomponents%2FCompareResults%2FhashToCommitLoader.ts&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=mozilla#diff-c3JjL2NvbXBvbmVudHMvQ29tcGFyZVJlc3VsdHMvaGFzaFRvQ29tbWl0TG9hZGVyLnRz) | 96.55% | [1 Missing :warning: ](https://app.codecov.io/gh/mozilla/perfcompare/pull/879?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=mozilla) |
| [src/components/CompareResults/loader.ts](https://app.codecov.io/gh/mozilla/perfcompare/pull/879?src=pr&el=tree&filepath=src%2Fcomponents%2FCompareResults%2Floader.ts&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=mozilla#diff-c3JjL2NvbXBvbmVudHMvQ29tcGFyZVJlc3VsdHMvbG9hZGVyLnRz) | 83.33% | [1 Missing :warning: ](https://app.codecov.io/gh/mozilla/perfcompare/pull/879?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=mozilla) |

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main     #879      +/-   ##
==========================================
+ Coverage   94.11%   94.14%   +0.03%     
==========================================
  Files          95       96       +1     
  Lines        2751     2767      +16     
  Branches      561      560       -1     
==========================================
+ Hits         2589     2605      +16     
  Misses        144      144              
  Partials       18       18              
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/mozilla/perfcompare/pull/879?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=mozilla).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=mozilla).

<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
- :package: [JS Bundle Analysis](https://docs.codecov.com/docs/javascript-bundle-analysis): Save yourself from yourself by tracking and limiting bundle sizes in JS merges.
</details>
author_association: NONE
user: login: Andrej1198
created_at: 2025-05-05T18:07:14Z
body: Hi @Carla-Moz I followed the advice of what @julienw mentioned and I re-used the existing results view rather than duplicating the code, let me know if you have any issues with what I did

I also put some parts of src/components/CompareResults/loader.ts in functions that I can use in my loader since otherwise I would have duplicated code, I hope I did this in a way you are okay with if not let me know and I can re-write my work :) 

I'm going to resolve the code coverage and ensure I get to the 93.69% threshold needed 
author_association: CONTRIBUTOR
user: login: Andrej1198
created_at: 2025-05-05T18:09:39Z
body: Also I don't have like 200 commits, I figured out why I was getting all those commits being created!
Sorry again for that mess 😭 
author_association: CONTRIBUTOR
user: login: kala-moz
created_at: 2025-05-05T21:02:23Z
body: > Hi @Carla-Moz I followed the advice of what @julienw mentioned and I re-used the existing results view rather than duplicating the code, let me know if you have any issues with what I did
> 
> I also put some parts of src/components/CompareResults/loader.ts in functions that I can use in my loader since otherwise I would have duplicated code, I hope I did this in a way you are okay with if not let me know and I can re-write my work :)
> 
> I'm going to resolve the code coverage and ensure I get to the 93.69% threshold needed

Hi @Andrej1198, sounds good. I'll look at it today. 
author_association: CONTRIBUTOR
user: login: kala-moz
created_at: 2025-05-05T21:09:51Z
body: @Andrej1198, could you add a deploy preview with a sample url containing the new parameters?
author_association: CONTRIBUTOR
user: login: julienw
created_at: 2025-05-06T09:17:21Z
body: Yay this looks much better :-) Thanks for the quick change ! I'll let Carla look at the code closely for the full review :-)


Just copying here my other comment about typing that might be hidden:

Here is an example where we also used 2 different loaders for the same view: https://github.com/mozilla/perfcompare/blob/ea276e9b317bb1d95e5e5971cefc7e9303fbd376/src/components/CompareResults/SubtestsResults/SubtestsResultsMain.tsx#L34-L36

See how we import both loader types and use them here, so that we don't lie to the type system and prevent future issues (if we change the return type of one loader but not the other one). I believe we should do the same also here.
author_association: CONTRIBUTOR
user: login: Andrej1198
created_at: 2025-05-06T14:33:44Z
body: > Yay this looks much better :-) Thanks for the quick change ! I'll let Carla look at the code closely for the full review :-)
> 
> Just copying here my other comment about typing that might be hidden:
> 
> Here is an example where we also used 2 different loaders for the same view:
> 
> https://github.com/mozilla/perfcompare/blob/ea276e9b317bb1d95e5e5971cefc7e9303fbd376/src/components/CompareResults/SubtestsResults/SubtestsResultsMain.tsx#L34-L36
> 
> See how we import both loader types and use them here, so that we don't lie to the type system and prevent future issues (if we change the return type of one loader but not the other one). I believe we should do the same also here.

I will get on and make sure this is done correctly, I was just getting up to code coverage sorry for the delay
author_association: CONTRIBUTOR
user: login: julienw
created_at: 2025-05-06T16:06:05Z
body: No worries at all @Andrej1198 ! Just wanted to push the recommendation before my PTOs :-)
author_association: CONTRIBUTOR
user: login: Andrej1198
created_at: 2025-05-07T17:18:56Z
body: How do I create a deploy preview url?
author_association: CONTRIBUTOR
user: login: Andrej1198
created_at: 2025-05-07T17:20:44Z
body: Never mind I figured out how to create a deploy preview!
author_association: CONTRIBUTOR
user: login: Andrej1198
created_at: 2025-05-07T17:29:46Z
body: Deploy Preview for my changes: https://deploy-preview-879--mozilla-perfcompare.netlify.app/compare-hash-results?baseHash=4185331032337250523&baseRepo=try&newHash=7181437775390339820&newRepo=try&framework=13&newHashDate=2025-04-25&baseHashDate=2025-04-25
author_association: CONTRIBUTOR
user: login: Andrej1198
created_at: 2025-05-07T17:54:19Z
body: @Carla-Moz I am now ready for a review! Let me know what you would like fixed! :) 
author_association: CONTRIBUTOR
user: login: kala-moz
created_at: 2025-05-07T22:37:44Z
body: It loads so much faster! Nice job, @Andrej1198 ! 
author_association: CONTRIBUTOR
user: login: kala-moz
created_at: 2025-05-07T23:04:35Z
body: Also, be sure to import both loader types for `ResultsView` like Julien mentioned. So you'll have to create a unique type for your `hashToCommitLoader`. After that, we're done! 
author_association: CONTRIBUTOR
user: login: Andrej1198
created_at: 2025-05-08T14:13:40Z
body: Hey @Carla-Moz I did 3 things:
1) Added better comments at the top of my loader function
2) I created a unique type for the hashToCommitLoader
3) I then also imported my unique loader type for ResultsView

Let me know if I missed anything
author_association: CONTRIBUTOR
user: login: Andrej1198
created_at: 2025-05-08T15:25:45Z
body: Hey @Carla-Moz I demoed my code to someone and I got a piece of feedback I'm incorporating

**What happened:**
When you push with ./mach try perf and get the compare-hash link you obviously do not have the commits available in try and ready for perfcompare to use.
The screen therefore shows an error saying "could not find commit, please check the date and hash combination"

**The advice**
Having an error message saying commit not found makes it look like you messed something up
There should be a still processing screen if possible

**What I did:**
I put a check so that if there is a not found error but the commit date is today, you will get an error message saying that the results are still processing.
author_association: CONTRIBUTOR
user: login: kala-moz
created_at: 2025-05-08T17:44:23Z
body: > Hey @Carla-Moz I demoed my code to someone and I got a piece of feedback I'm incorporating
> 
> **What happened:** When you push with ./mach try perf and get the compare-hash link you obviously do not have the commits available in try and ready for perfcompare to use. The screen therefore shows an error saying "could not find commit, please check the date and hash combination"
> 
> **The advice** Having an error message saying commit not found makes it look like you messed something up There should be a still processing screen if possible
> 
> **What I did:** I put a check so that if there is a not found error but the commit date is today, you will get an error message saying that the results are still processing.

Okay, let me know when you're ready
author_association: CONTRIBUTOR
user: login: Andrej1198
created_at: 2025-05-08T18:07:15Z
body: @Carla-Moz it's now ready!
author_association: CONTRIBUTOR
user: login: Andrej1198
created_at: 2025-05-08T18:10:00Z
body: Looking now I see that I used present tense when in fact it was already added at that point my apologies
author_association: CONTRIBUTOR