Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 16 additions & 8 deletions backend/kernelCI_app/queries/tree.py
Original file line number Diff line number Diff line change
Expand Up @@ -1059,7 +1059,7 @@ def get_tree_compare_boots_tests_diff(
**filter_sql.params,
}

cache_key = "treeCompareBootsTestsDiff"
cache_key = "treeCompareBootsTestsDiffIds"
cache_params = {
**params,
"data_type": data_type,
Expand Down Expand Up @@ -1118,6 +1118,7 @@ def get_tree_compare_boots_tests_diff(
NULLIF(t.environment_misc->>'platform', ''),
%(unknown_string)s
) AS platform,
t.id AS item_id,
CASE
WHEN UPPER(t.status) = 'PASS' THEN 'PASS'
WHEN UPPER(t.status) = 'FAIL' THEN 'FAIL'
Expand All @@ -1144,12 +1145,14 @@ def get_tree_compare_boots_tests_diff(
t._timestamp DESC NULLS LAST
),
SIDE_A AS (
SELECT path, config_name, architecture, platform, grouped_status
SELECT path, config_name, architecture, platform, grouped_status,
item_id
FROM TEST_ROWS
WHERE git_commit_hash = %(hash_a)s
),
SIDE_B AS (
SELECT path, config_name, architecture, platform, grouped_status
SELECT path, config_name, architecture, platform, grouped_status,
item_id
FROM TEST_ROWS
WHERE git_commit_hash = %(hash_b)s
)
Expand All @@ -1159,7 +1162,9 @@ def get_tree_compare_boots_tests_diff(
COALESCE(a.architecture, b.architecture) AS architecture,
COALESCE(a.platform, b.platform) AS platform,
a.grouped_status AS status_a,
b.grouped_status AS status_b
b.grouped_status AS status_b,
a.item_id AS id_a,
b.item_id AS id_b
FROM
SIDE_A a
FULL OUTER JOIN SIDE_B b ON (
Expand Down Expand Up @@ -1614,7 +1619,7 @@ def get_tree_compare_builds_diff(
commit_hashes = [hash_a, hash_b]
filter_sql = build_build_compare_filter_clauses(filters)

cache_key = "treeCompareBuildsDiff"
cache_key = "treeCompareBuildsDiffIds"
params = {
"hash_a": hash_a,
"hash_b": hash_b,
Expand Down Expand Up @@ -1669,6 +1674,7 @@ def get_tree_compare_builds_diff(
COALESCE(NULLIF(b.architecture, ''), %(unknown_string)s)
AS architecture,
COALESCE(NULLIF(b.compiler, ''), %(unknown_string)s) AS compiler,
b.id AS item_id,
CASE
WHEN UPPER(b.status) = 'PASS' THEN 'PASS'
WHEN UPPER(b.status) = 'FAIL' THEN 'FAIL'
Expand All @@ -1689,12 +1695,12 @@ def get_tree_compare_builds_diff(
b._timestamp DESC NULLS LAST
),
SIDE_A AS (
SELECT config_name, architecture, compiler, grouped_status
SELECT config_name, architecture, compiler, grouped_status, item_id
FROM BUILD_ROWS
WHERE git_commit_hash = %(hash_a)s
),
SIDE_B AS (
SELECT config_name, architecture, compiler, grouped_status
SELECT config_name, architecture, compiler, grouped_status, item_id
FROM BUILD_ROWS
WHERE git_commit_hash = %(hash_b)s
)
Expand All @@ -1703,7 +1709,9 @@ def get_tree_compare_builds_diff(
COALESCE(a.architecture, b.architecture) AS architecture,
COALESCE(a.compiler, b.compiler) AS compiler,
a.grouped_status AS status_a,
b.grouped_status AS status_b
b.grouped_status AS status_b,
a.item_id AS id_a,
b.item_id AS id_b
FROM
SIDE_A a
FULL OUTER JOIN SIDE_B b ON (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,8 @@ def test_compare_builds_diff_joins_and_diffs_in_sql(
self.assertIn("ELSE 'INCONCLUSIVE'", executed_query)
self.assertNotIn("incidents", executed_query)
self.assertNotIn("FROM tests", executed_query)
self.assertIn("AS id_a", executed_query)
self.assertIn("AS id_b", executed_query)
self.assertEqual(params["commit_hashes"], ["hash_a", "hash_b"])

@patch("kernelCI_app.queries.tree.connection")
Expand Down Expand Up @@ -224,4 +226,6 @@ def test_compare_boots_tests_diff_uses_latest_wins_and_diffs_in_sql(
self.assertIn("NULLIF(b.architecture, '')", executed_query)
self.assertNotIn("WHEN UPPER(t.status) = 'FAIL' THEN 2", executed_query)
self.assertNotIn("ARRAY_AGG", executed_query)
self.assertIn("AS id_a", executed_query)
self.assertIn("AS id_b", executed_query)
self.assertEqual(params["commit_hashes"], ["hash_a", "hash_b"])
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
"compiler": "gcc-12",
"status_a": "PASS",
"status_b": "FAIL",
"id_a": "build-a",
"id_b": "build-b",
}


Expand Down
2 changes: 2 additions & 0 deletions backend/kernelCI_app/typeModels/treeCompare.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ class CompareBuildDiffRow(BaseModel):
compiler: str
status_a: Optional[CompareGroupedStatus] = None
status_b: Optional[CompareGroupedStatus] = None
id_a: Optional[str] = None
id_b: Optional[str] = None


class TreeCompareBuildsResponse(RootModel[List[CompareBuildDiffRow]]):
Expand Down
2 changes: 2 additions & 0 deletions backend/kernelCI_app/typeModels/treeDetails.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@ class TreeCompareTest(BaseModel):
platform: str
status_a: Optional[GroupedStatusLiteral]
status_b: Optional[GroupedStatusLiteral]
id_a: Optional[str] = None
id_b: Optional[str] = None


class TreeCompareBootsTestsResponse(RootModel[List[TreeCompareTest]]):
Expand Down
24 changes: 24 additions & 0 deletions backend/schema.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2717,6 +2717,18 @@ components:
- type: 'null'
default: null
title: Status B
id_a:
anyOf:
- type: string
- type: 'null'
default: null
title: Id A
id_b:
anyOf:
- type: string
- type: 'null'
default: null
title: Id B
required:
- config_name
- architecture
Expand Down Expand Up @@ -4813,6 +4825,18 @@ components:
type: string
- type: 'null'
title: Status B
id_a:
anyOf:
- type: string
- type: 'null'
default: null
title: Id A
id_b:
anyOf:
- type: string
- type: 'null'
default: null
title: Id B
required:
- path
- config_name
Expand Down
47 changes: 45 additions & 2 deletions dashboard/e2e/tree-compare.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ const FULL_HASH_LENGTH = 40;
const HASH_A = 'a'.repeat(FULL_HASH_LENGTH);
const HASH_B = 'b'.repeat(FULL_HASH_LENGTH);

test('loads revisions and comparison data from the API', async ({ page }) => {
test('loads comparison data and opens a side-by-side details drawer', async ({
page,
}) => {
await page.route('**/api/tree/linux/master/commits?**', route =>
route.fulfill({
json: [
Expand All @@ -29,6 +31,8 @@ test('loads revisions and comparison data from the API', async ({ page }) => {
compiler: 'clang-17',
status_a: 'PASS',
status_b: 'FAIL',
id_a: 'build-a',
id_b: 'build-b',
},
],
}),
Expand Down Expand Up @@ -90,12 +94,51 @@ test('loads revisions and comparison data from the API', async ({ page }) => {
}),
);

await page.route('**/api/build/build-a**', route =>
route.fulfill({
json: {
id: 'build-a',
status: 'PASS',
build_origin: 'maestro',
timestamp: '2026-07-14T10:00:00Z',
log_excerpt: 'side A build log',
architecture: 'arm64',
git_commit_hash: HASH_A,
tree_name: 'linux',
git_repository_branch: 'master',
},
}),
);

await page.route('**/api/build/build-b**', route =>
route.fulfill({
json: {
id: 'build-b',
status: 'FAIL',
build_origin: 'maestro',
timestamp: '2026-07-13T10:00:00Z',
log_excerpt: 'side B build log',
architecture: 'arm64',
git_commit_hash: HASH_B,
tree_name: 'linux',
git_repository_branch: 'master',
},
}),
);

await page.goto(
`/tree/linux/master/compare?hashA=${HASH_A}&hashB=${HASH_B}&origin=maestro`,
);

await expect(page.getByText('Tree summary')).toBeVisible();
await expect(page.getByText('Changed results')).toBeVisible();
await expect(page.getByText('defconfig+allmodconfig')).toBeVisible();
await expect(page.getByText('Regression')).toBeVisible();
await expect(page.getByText('Regression', { exact: true })).toBeVisible();

await page.getByText('defconfig+allmodconfig').click();

await expect(page.getByRole('dialog')).toBeVisible();
await expect(page.getByText('Log Viewer')).toBeVisible();
await expect(page.getByText('side A build log')).toBeVisible();
await expect(page.getByText('side B build log')).toBeVisible();
});
35 changes: 23 additions & 12 deletions dashboard/src/components/Log/LogViewerCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@ import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/Tooltip';

import type { LogData } from '@/hooks/useLogData';
import { valueOrEmpty } from '@/lib/string';
import { cn } from '@/lib/utils';

interface ILogViewerCard {
isLoading?: boolean;
logData?: LogData;
variant?: 'full' | 'modal';
/** `compare` drops the status/title chip: the compare sheet header already shows both. */
variant?: 'full' | 'modal' | 'compare';
}

const getTreeBranchHash = (
Expand Down Expand Up @@ -121,15 +123,22 @@ export const LogViewerCard = ({
return `${hardware} (${architecture})`;
}, [hardware, architecture]);

const showTitleChip = variant !== 'compare';

return (
<div className="gap-0">
<div className="grid grid-cols-1 items-start justify-between p-4 text-lg md:grid-cols-2">
<div
className={cn(
'grid grid-cols-1 items-start justify-between p-4 text-lg',
showTitleChip && 'md:grid-cols-2',
)}
>
{isLoading ? (
<FormattedMessage id="global.loading" />
) : (
<>
<div>
<div className="font-medium">
<div className="font-medium break-words">
{getTreeBranchHash(
logData?.tree_name,
logData?.git_repository_branch,
Expand All @@ -146,16 +155,18 @@ export const LogViewerCard = ({
/>
</div>
)}
{variant === 'modal' && linkComponent}
{variant !== 'full' && linkComponent}
</div>
<Tooltip>
<TooltipTrigger>
<div className="flex max-w-max flex-row items-center gap-2 rounded-md bg-gray-200 p-3">
<StatusIcon status={logData?.status} /> {logData?.title}
</div>
</TooltipTrigger>
<TooltipContent>{logData?.status}</TooltipContent>
</Tooltip>
{showTitleChip && (
<Tooltip>
<TooltipTrigger>
<div className="flex max-w-max flex-row items-center gap-2 rounded-md bg-gray-200 p-3">
<StatusIcon status={logData?.status} /> {logData?.title}
</div>
</TooltipTrigger>
<TooltipContent>{logData?.status}</TooltipContent>
</Tooltip>
)}
</>
)}
</div>
Expand Down
1 change: 1 addition & 0 deletions dashboard/src/locales/messages/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,7 @@ export const messages = {
'treeCompare.changed': 'Changed',
'treeCompare.description':
'Compare pass/fail counts between two revisions on the same tree and branch.',
'treeCompare.detail.missingSide': 'No result on this side',
'treeCompare.drilldownHint':
'Individual builds, boots, and tests that changed between Side A and Side B.',
'treeCompare.failures.change': 'Change',
Expand Down
Loading
Loading