Skip to content

Commit 2855a05

Browse files
feat(tree-compare): implement frontend for tree comparison
Signed-off-by: Felipe Bergamin <felipebergamin6@gmail.com>
1 parent 8dcfe9d commit 2855a05

15 files changed

Lines changed: 1056 additions & 2 deletions

File tree

dashboard/e2e/tree-compare.spec.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import { expect, test } from '@playwright/test';
2+
3+
const FULL_HASH_LENGTH = 40;
4+
const HASH_A = 'a'.repeat(FULL_HASH_LENGTH);
5+
const HASH_B = 'b'.repeat(FULL_HASH_LENGTH);
6+
7+
test('loads revisions and comparison data from the API', async ({ page }) => {
8+
await page.route('**/api/tree/linux/master/commits?**', route =>
9+
route.fulfill({
10+
json: [
11+
{
12+
git_commit_hash: HASH_A,
13+
last_checkout: '2026-07-14T10:00:00Z',
14+
},
15+
{
16+
git_commit_hash: HASH_B,
17+
last_checkout: '2026-07-13T10:00:00Z',
18+
},
19+
],
20+
}),
21+
);
22+
23+
await page.route('**/api/tree/linux/master/compare?**', route =>
24+
route.fulfill({
25+
json: {
26+
treeName: 'linux',
27+
branch: 'master',
28+
gitUrl: 'https://git.kernel.org/linux.git',
29+
summary: {
30+
builds: {
31+
sideA: { pass: 42, fail: 1, inconclusive: 0 },
32+
sideB: { pass: 40, fail: 3, inconclusive: 0 },
33+
delta: { pass: -2, fail: 2 },
34+
},
35+
boots: {
36+
sideA: { pass: 20, fail: 0, inconclusive: 1 },
37+
sideB: { pass: 18, fail: 2, inconclusive: 1 },
38+
delta: { pass: -2, fail: 2 },
39+
},
40+
tests: {
41+
sideA: { pass: 100, fail: 5, inconclusive: 2 },
42+
sideB: { pass: 95, fail: 10, inconclusive: 2 },
43+
delta: { pass: -5, fail: 5 },
44+
},
45+
},
46+
groups: {
47+
builds: [],
48+
boots: [],
49+
tests: [],
50+
},
51+
},
52+
}),
53+
);
54+
55+
await page.goto(
56+
`/tree/linux/master/compare?hashA=${HASH_A}&hashB=${HASH_B}&origin=maestro`,
57+
);
58+
59+
await expect(page.getByText('Tree summary')).toBeVisible();
60+
await expect(page.getByText('42', { exact: true }).first()).toBeVisible();
61+
await expect(page.getByText('100', { exact: true }).first()).toBeVisible();
62+
await expect(page.getByText(/data is mocked/i)).toHaveCount(0);
63+
});

dashboard/src/api/treeCompare.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import type { UseQueryResult } from '@tanstack/react-query';
2+
import { useQuery } from '@tanstack/react-query';
3+
4+
import type { TreeCompareData } from '@/types/tree/TreeCompare';
5+
6+
import { RequestData } from './commonRequest';
7+
8+
const fetchTreeCompare = async ({
9+
treeName,
10+
branch,
11+
hashA,
12+
hashB,
13+
origin,
14+
}: {
15+
treeName: string;
16+
branch: string;
17+
hashA: string;
18+
hashB: string;
19+
origin: string;
20+
}): Promise<TreeCompareData> =>
21+
RequestData.get<TreeCompareData>(`/api/tree/${treeName}/${branch}/compare`, {
22+
params: {
23+
hash_a: hashA,
24+
hash_b: hashB,
25+
origin,
26+
},
27+
});
28+
29+
export const useTreeCompare = ({
30+
treeName,
31+
branch,
32+
hashA,
33+
hashB,
34+
origin,
35+
}: {
36+
treeName: string;
37+
branch: string;
38+
hashA: string;
39+
hashB: string;
40+
origin: string;
41+
}): UseQueryResult<TreeCompareData> =>
42+
useQuery({
43+
queryKey: ['treeCompare', treeName, branch, hashA, hashB, origin],
44+
queryFn: () => fetchTreeCompare({ treeName, branch, hashA, hashB, origin }),
45+
enabled: !!hashA && !!hashB,
46+
});

dashboard/src/locales/messages/index.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -373,10 +373,32 @@ export const messages = {
373373
'title.hardwareDetails': 'Hardware: {hardwareName}',
374374
'title.issueDetails': 'Issue: {issueName}',
375375
'title.testDetails': 'Test: {testName}',
376+
'title.treeCompare': 'Compare: {treeName}',
376377
'title.treeDetails': 'Tree: {treeName}',
377378
'tree.details': 'Trees Details',
378379
'tree.path': 'Trees',
379380
'tree.searchPlaceholder': 'Search by tree, branch or tag with a regex',
381+
'treeCompare.backToDetails': 'Back to tree details',
382+
'treeCompare.breadcrumb': 'Compare',
383+
'treeCompare.breakdownTitle': 'Grouped breakdown',
384+
'treeCompare.changed': 'Changed',
385+
'treeCompare.delta': 'Delta',
386+
'treeCompare.deltaFail': 'Δ fail',
387+
'treeCompare.deltaPass': 'Δ pass',
388+
'treeCompare.description':
389+
'Compare pass/fail counts between two revisions on the same tree and branch.',
390+
'treeCompare.group.boots': 'Platform',
391+
'treeCompare.group.builds': 'Config',
392+
'treeCompare.group.tests': 'Path',
393+
'treeCompare.openCompare': 'Compare revisions',
394+
'treeCompare.selectRevision': 'Select a revision',
395+
'treeCompare.sideA': 'Side A',
396+
'treeCompare.sideB': 'Side B',
397+
'treeCompare.suggestion.branchHead': 'Branch head',
398+
'treeCompare.suggestion.previous': 'Previous commit',
399+
'treeCompare.suggestion.swap': 'Swap sides',
400+
'treeCompare.suggestions': 'Suggestions',
401+
'treeCompare.summaryTitle': 'Tree summary',
380402
'treeDetails.bootsHistory': 'Boots History',
381403
'treeDetails.branch': 'Branch',
382404
'treeDetails.buildsHistory': 'Builds History',
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { Link } from '@tanstack/react-router';
2+
import type { JSX } from 'react';
3+
4+
import { GitCompareArrows } from 'lucide-react';
5+
import { FormattedMessage } from 'react-intl';
6+
7+
import { Button } from '@/components/ui/button';
8+
9+
interface TreeCompareLinkProps {
10+
treeName: string;
11+
branch: string;
12+
hash: string;
13+
origin: string;
14+
}
15+
16+
export function TreeCompareLink({
17+
treeName,
18+
branch,
19+
hash,
20+
origin,
21+
}: TreeCompareLinkProps): JSX.Element {
22+
return (
23+
<Button variant="outline" size="sm" asChild>
24+
<Link
25+
to="/tree/$treeName/$branch/compare"
26+
params={{ treeName, branch }}
27+
search={{ hashA: hash, hashB: '', origin }}
28+
state={s => s}
29+
>
30+
<GitCompareArrows className="mr-2 h-4 w-4" />
31+
<FormattedMessage id="treeCompare.openCompare" />
32+
</Link>
33+
</Button>
34+
);
35+
}

0 commit comments

Comments
 (0)