Skip to content

Commit 52b19d5

Browse files
committed
feat: Use stable key for the selected trees on hardware details page.
* Use a hash key from git_url, tree_name and branch to be sure we can select trees, without need of compatible ordering between frontend and backend. Closes #1909 Signed-off-by: Alan Peixinho <alan.peixinho@profusion.mobi>
1 parent 76bf104 commit 52b19d5

19 files changed

Lines changed: 225 additions & 99 deletions

File tree

backend/generate-schema.sh

100644100755
File mode changed.
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,9 @@
1+
import hashlib
2+
13
SELECTED_HEAD_TREE_VALUE = "head"
4+
TREE_KEY_HASH_LENGTH = 14
5+
6+
7+
def make_tree_key(tree_name: str, branch: str, url: str) -> str:
8+
raw = f"{tree_name}|{branch}|{url}"
9+
return hashlib.sha256(raw.encode()).hexdigest()[:TREE_KEY_HASH_LENGTH]

backend/kernelCI_app/constants/localization.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,19 @@ class DocStrings:
8989

9090
HARDWARE_DETAILS_ORIGIN_DESCRIPTION = "Origin of the tests of the hardware"
9191
HARDWARE_DETAILS_SEL_COMMITS_DESCRIPTION = (
92-
"Dictionary mapping tree names to selected commit hashes"
92+
"Dictionary mapping stable tree keys to selected commit hashes. "
93+
"Keys are the first 14 hexadecimal characters of SHA-256 over "
94+
"'tree_name|git_repository_branch|git_repository_url', matching "
95+
"Tree.index values returned by the hardware details endpoints. "
96+
"Values are a git commit hash or 'head' to use the tree head commit. "
97+
"An empty object selects all trees with their head commits. "
98+
"Legacy clients may send numeric string keys ('0', '1', ...) that "
99+
"map to trees by position in the sorted tree list."
100+
)
101+
HARDWARE_DETAILS_TREE_INDEX_DESCRIPTION = (
102+
"Stable identifier for a tree, derived as the first 14 hexadecimal "
103+
"characters of SHA-256 over "
104+
"'tree_name|git_repository_branch|git_repository_url'"
93105
)
94106

95107
HARDWARE_LISTING_ORIGIN_DESCRIPTION = "Origin of the hardware"

backend/kernelCI_app/helpers/hardwareDetails.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -92,15 +92,21 @@ def get_displayed_commit(*, tree: Tree, selected_commit: Optional[str]):
9292
return selected_commit
9393

9494

95+
def _is_legacy_numeric_keys(keys: Dict[str, str]) -> bool:
96+
return bool(keys) and all(k.isdigit() for k in keys)
97+
98+
9599
def get_trees_with_selected_commit(
96100
*, trees: List[Tree], selected_commits: Dict[str, str]
97101
) -> List[Tree]:
98102
selected: List[Tree] = []
99103

100-
for tree in trees:
101-
tree_idx = tree.index
104+
is_legacy = _is_legacy_numeric_keys(selected_commits)
105+
106+
for i, tree in enumerate(trees):
107+
lookup_key = str(i) if is_legacy else tree.index
102108

103-
raw_selected_commit = selected_commits.get(tree_idx)
109+
raw_selected_commit = selected_commits.get(lookup_key)
104110

105111
is_tree_selected = raw_selected_commit is not None
106112

backend/kernelCI_app/queries/hardware.py

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from django.db import connection
55

66
from kernelCI_app.cache import get_query_cache, set_query_cache
7+
from kernelCI_app.constants.hardwareDetails import make_tree_key
78
from kernelCI_app.helpers.database import dict_fetchall
89
from kernelCI_app.queries.duration import (
910
get_boot_test_duration_clause,
@@ -782,6 +783,8 @@ def get_hardware_trees_head_commits(
782783
TH.git_repository_url,
783784
TH.git_commit_hash
784785
) TH.tree_name,
786+
TH.git_repository_branch,
787+
TH.git_repository_url,
785788
TH.git_commit_hash
786789
FROM
787790
tests
@@ -816,8 +819,15 @@ def get_hardware_trees_head_commits(
816819
cursor.execute(query, params)
817820
tree_records = dict_fetchall(cursor)
818821
trees = [
819-
(str(idx), tree["git_commit_hash"])
820-
for (idx, tree) in enumerate(tree_records)
822+
(
823+
make_tree_key(
824+
tree["tree_name"] or "",
825+
tree.get("git_repository_branch", "") or "",
826+
tree.get("git_repository_url", "") or "",
827+
),
828+
tree["git_commit_hash"],
829+
)
830+
for tree in tree_records
821831
]
822832
set_query_cache(key=cache_key, params=cache_params, rows=trees)
823833

@@ -892,10 +902,15 @@ def get_hardware_trees_data(
892902
tree_records = dict_fetchall(cursor)
893903

894904
trees = []
895-
for idx, tree in enumerate(tree_records):
905+
for tree in tree_records:
906+
tree_index = make_tree_key(
907+
tree["tree_name"] or "",
908+
tree["git_repository_branch"] or "",
909+
tree["git_repository_url"] or "",
910+
)
896911
trees.append(
897912
Tree(
898-
index=str(idx),
913+
index=tree_index,
899914
tree_name=tree["tree_name"],
900915
origin=tree["origin"],
901916
git_repository_branch=tree["git_repository_branch"],

backend/kernelCI_app/tests/unitTests/helpers/fixtures/hardware_details_data.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from collections import defaultdict
22

3+
from kernelCI_app.constants.hardwareDetails import make_tree_key
34
from kernelCI_app.typeModels.common import StatusCount
45
from kernelCI_app.typeModels.commonDetails import (
56
TestArchSummaryItem,
@@ -10,8 +11,13 @@
1011

1112
def create_tree(**overrides):
1213
"""Create Tree."""
14+
tree_name = overrides.get("tree_name", "mainline")
15+
branch = overrides.get("git_repository_branch", "master")
16+
url = overrides.get("git_repository_url", "https://git.kernel.org")
17+
default_index = make_tree_key(tree_name, branch, url)
18+
1319
base_tree = Tree(
14-
index="1",
20+
index=overrides.get("index", default_index),
1521
origin="test",
1622
tree_name="mainline",
1723
git_repository_branch="master",
@@ -29,12 +35,16 @@ def create_tree(**overrides):
2935
return base_tree
3036

3137

38+
BASE_TREE_KEY = make_tree_key("mainline", "master", "https://git.kernel.org")
39+
DIFF_TREE_KEY = make_tree_key("stable", "linux-5.4.y", "https://git.kernel.org")
40+
41+
3242
def create_tree_status_summary(**overrides):
3343
"""Create tree status summary."""
3444
summary = defaultdict(lambda: defaultdict(lambda: defaultdict(int)))
3545

36-
summary["1"]["builds"]["PASS"] = 5
37-
summary["2"]["builds"]["FAIL"] = 2
46+
summary[BASE_TREE_KEY]["builds"]["PASS"] = 5
47+
summary[DIFF_TREE_KEY]["builds"]["FAIL"] = 2
3848

3949
for tree_id, tree_data in overrides.items():
4050
for category, status_data in tree_data.items():
@@ -127,7 +137,6 @@ def create_test_summary(**overrides):
127137
base_tree = create_tree()
128138

129139
tree_with_different_commit = create_tree(
130-
index="2",
131140
tree_name="stable",
132141
git_repository_branch="linux-5.4.y",
133142
head_git_commit_name="commit2",

backend/kernelCI_app/tests/unitTests/helpers/hardwareDetails_helpers_test.py

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@
4141
update_issues,
4242
)
4343
from kernelCI_app.tests.unitTests.helpers.fixtures.hardware_details_data import (
44+
BASE_TREE_KEY,
45+
DIFF_TREE_KEY,
4446
base_tree,
4547
base_tree_status_summary,
4648
create_test_summary,
@@ -126,9 +128,24 @@ def test_get_displayed_commit_with_head_tree_value(self):
126128

127129
class TestGetTreesWithSelectedCommit:
128130
def test_get_trees_with_selected_commit(self):
129-
"""Test get_trees_with_selected_commit function."""
131+
"""Test get_trees_with_selected_commit function with stable keys."""
130132
trees = [base_tree, tree_with_different_commit]
131-
selected_commits = {"1": "custom123", "2": None}
133+
selected_commits = {BASE_TREE_KEY: "custom123", DIFF_TREE_KEY: None}
134+
135+
result = get_trees_with_selected_commit(
136+
trees=trees, selected_commits=selected_commits
137+
)
138+
139+
assert len(result) == 2
140+
assert result[0].head_git_commit_hash == "custom123"
141+
assert result[0].is_selected is True
142+
assert result[1].head_git_commit_hash == "def456"
143+
assert result[1].is_selected is False
144+
145+
def test_get_trees_with_selected_commit_legacy_numeric_keys(self):
146+
"""Test backward compat: numeric keys still work for one release."""
147+
trees = [base_tree, tree_with_different_commit]
148+
selected_commits = {"0": "custom123"}
132149

133150
result = get_trees_with_selected_commit(
134151
trees=trees, selected_commits=selected_commits

backend/kernelCI_app/typeModels/hardwareDetails.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ class CommitHistoryValidCheckout(BaseModel):
7979

8080

8181
class Tree(BaseModel):
82-
index: str
82+
index: str = Field(description=DocStrings.HARDWARE_DETAILS_TREE_INDEX_DESCRIPTION)
8383
origin: Origin
8484
tree_name: Optional[str]
8585
git_repository_branch: Optional[str]

backend/kernelCI_app/views/hardwareDetailsSummaryView.py

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from rest_framework.views import APIView
1414

1515
from kernelCI_app.constants.general import UNKNOWN_STRING
16+
from kernelCI_app.constants.hardwareDetails import make_tree_key
1617
from kernelCI_app.constants.localization import ClientStrings
1718
from kernelCI_app.helpers.errorHandling import create_api_error_response
1819
from kernelCI_app.helpers.filters import (
@@ -368,8 +369,12 @@ def aggregate_common(
368369
t.head_git_commit_hash or "",
369370
),
370371
)
371-
for i, tree in enumerate(sorted_trees):
372-
tree.index = str(i)
372+
for tree in sorted_trees:
373+
tree.index = make_tree_key(
374+
tree.tree_name or "",
375+
tree.git_repository_branch or "",
376+
tree.git_repository_url or "",
377+
)
373378

374379
return sorted_trees, sorted(all_compatibles)
375380

@@ -471,19 +476,32 @@ def valid_filter_status(self) -> bool:
471476
)
472477
)
473478

479+
def _is_legacy_numeric_keys(self, keys: dict[str, str]) -> bool:
480+
return all(k.isdigit() for k in keys)
481+
474482
def select_commits_hashes(
475483
self,
476-
tree_heads: list[(str, str)],
484+
tree_heads: list[tuple[str, str]],
477485
selected_commits: Optional[dict[str, str]] = None,
478486
):
479487
selected_commit_hashes = []
480488
if selected_commits:
481-
for idx, head in tree_heads:
482-
if idx in self.selected_commits:
483-
selected_commit = self.selected_commits.get(idx, "head")
484-
selected_commit_hashes.append(
485-
head if selected_commit == "head" else selected_commit
486-
)
489+
if self._is_legacy_numeric_keys(self.selected_commits):
490+
indexed_heads = list(enumerate(tree_heads))
491+
for i, (_, head) in indexed_heads:
492+
str_i = str(i)
493+
if str_i in self.selected_commits:
494+
selected_commit = self.selected_commits.get(str_i, "head")
495+
selected_commit_hashes.append(
496+
head if selected_commit == "head" else selected_commit
497+
)
498+
else:
499+
for key, head in tree_heads:
500+
if key in self.selected_commits:
501+
selected_commit = self.selected_commits.get(key, "head")
502+
selected_commit_hashes.append(
503+
head if selected_commit == "head" else selected_commit
504+
)
487505
else:
488506
selected_commit_hashes = [head for (_, head) in tree_heads]
489507
return selected_commit_hashes

backend/schema.yml

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2849,7 +2849,13 @@ components:
28492849
selectedCommits:
28502850
additionalProperties:
28512851
type: string
2852-
description: Dictionary mapping tree names to selected commit hashes
2852+
description: Dictionary mapping stable tree keys to selected commit hashes.
2853+
Keys are the first 14 hexadecimal characters of SHA-256 over 'tree_name|git_repository_branch|git_repository_url',
2854+
matching Tree.index values returned by the hardware details endpoints.
2855+
Values are a git commit hash or 'head' to use the tree head commit. An
2856+
empty object selects all trees with their head commits. Legacy clients
2857+
may send numeric string keys ('0', '1', ...) that map to trees by position
2858+
in the sorted tree list.
28532859
title: Selectedcommits
28542860
type: object
28552861
filter:
@@ -3885,7 +3891,7 @@ components:
38853891
id:
38863892
$ref: '#/components/schemas/Test__Id'
38873893
status:
3888-
$ref: '#/components/schemas/Test__Status'
3894+
$ref: '#/components/schemas/StatusValues'
38893895
git_commit_hash:
38903896
$ref: '#/components/schemas/Checkout__GitCommitHash'
38913897
required:
@@ -4052,6 +4058,8 @@ components:
40524058
Tree:
40534059
properties:
40544060
index:
4061+
description: Stable identifier for a tree, derived as the first 14 hexadecimal
4062+
characters of SHA-256 over 'tree_name|git_repository_branch|git_repository_url'
40554063
title: Index
40564064
type: string
40574065
origin:

0 commit comments

Comments
 (0)