Skip to content

Commit c3220cf

Browse files
author
Gustavo Flores
committed
feat: add hardware selectors and revision API
1 parent 76bf104 commit c3220cf

8 files changed

Lines changed: 554 additions & 1 deletion

File tree

backend/kernelCI_app/queries/hardware.py

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,111 @@ def get_hardware_listing_data(
201201
return cursor.fetchall()
202202

203203

204+
def get_hardware_selectors(origin: str) -> list[dict]:
205+
cache_key = "hardwareSelectors"
206+
cache_params = {"origin": origin}
207+
208+
rows = get_query_cache(cache_key, cache_params)
209+
if rows is not None:
210+
return rows
211+
212+
params = {"origin": origin}
213+
214+
query = """
215+
SELECT DISTINCT ON (
216+
c.tree_name,
217+
c.git_repository_url,
218+
c.git_repository_branch,
219+
c.git_commit_hash,
220+
c.git_commit_name
221+
)
222+
c.tree_name,
223+
c.git_repository_url,
224+
c.git_repository_branch,
225+
c.git_commit_hash,
226+
c.git_commit_name,
227+
c.start_time
228+
FROM checkouts c
229+
INNER JOIN builds b ON b.checkout_id = c.id
230+
INNER JOIN tests t ON t.build_id = b.id
231+
WHERE
232+
t.origin = %(origin)s
233+
AND t.start_time > (NOW() - INTERVAL '15 days')
234+
AND t.environment_misc ->> 'platform' IS NOT NULL
235+
ORDER BY
236+
c.tree_name,
237+
c.git_repository_url,
238+
c.git_repository_branch,
239+
c.git_commit_hash,
240+
c.git_commit_name,
241+
c.start_time DESC;
242+
"""
243+
244+
with connection.cursor() as cursor:
245+
cursor.execute(query, params)
246+
rows = dict_fetchall(cursor)
247+
248+
set_query_cache(key=cache_key, params=cache_params, rows=rows)
249+
return rows
250+
251+
252+
def get_hardware_listing_data_by_revision(
253+
*,
254+
origin: str,
255+
tree_name: str,
256+
git_repository_url: str,
257+
git_repository_branch: str,
258+
git_commit_hash: str,
259+
) -> list[dict]:
260+
count_clauses = _get_hardware_listing_count_clauses()
261+
params = {
262+
"origin": origin,
263+
"tree_name": tree_name,
264+
"git_repository_url": git_repository_url,
265+
"git_repository_branch": git_repository_branch,
266+
"git_commit_hash": git_commit_hash,
267+
}
268+
269+
query = f"""
270+
WITH relevant_tests AS (
271+
SELECT
272+
tests.environment_compatible AS hardware,
273+
tests.environment_misc ->> 'platform' AS platform,
274+
tests.status,
275+
tests.path,
276+
tests.id,
277+
b.id AS build_id,
278+
b.status AS build_status
279+
FROM
280+
checkouts c
281+
INNER JOIN builds b ON b.checkout_id = c.id
282+
INNER JOIN tests ON tests.build_id = b.id
283+
WHERE
284+
c.tree_name = %(tree_name)s
285+
AND c.git_repository_url = %(git_repository_url)s
286+
AND c.git_repository_branch = %(git_repository_branch)s
287+
AND c.git_commit_hash = %(git_commit_hash)s
288+
AND tests.origin = %(origin)s
289+
AND tests.environment_misc ->> 'platform' IS NOT NULL
290+
)
291+
SELECT
292+
relevant_tests.platform,
293+
relevant_tests.hardware,
294+
{count_clauses}
295+
FROM
296+
relevant_tests
297+
GROUP BY
298+
relevant_tests.platform,
299+
relevant_tests.hardware
300+
ORDER BY
301+
relevant_tests.platform ASC
302+
"""
303+
304+
with connection.cursor() as cursor:
305+
cursor.execute(query, params)
306+
return dict_fetchall(cursor)
307+
308+
204309
def get_hardware_listing_data_bulk(
205310
keys: list[tuple[str, str]],
206311
start_date: datetime,

backend/kernelCI_app/tests/unitTests/url_patterns_test.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,3 +133,11 @@ def test_tree_commit_hash_patterns(self):
133133
url = reverse("treeDetailsSummaryView", kwargs={"commit_hash": commit_hash})
134134
resolved = resolve(url)
135135
assert resolved.kwargs["commit_hash"] == commit_hash
136+
137+
def test_hardware_selectors_route(self):
138+
url = reverse("hardwareSelectors")
139+
resolved = resolve(url)
140+
141+
assert url == "/api/hardware/selectors/"
142+
assert resolved.url_name == "hardwareSelectors"
143+
assert "hardware_id" not in resolved.kwargs
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
from typing import Annotated
2+
3+
from pydantic import BaseModel, BeforeValidator, Field
4+
5+
from kernelCI_app.constants.general import DEFAULT_ORIGIN
6+
from kernelCI_app.constants.localization import DocStrings
7+
8+
9+
class HardwareListingByRevisionQueryParamsDocumentationOnly(BaseModel):
10+
origin: Annotated[
11+
str,
12+
Field(
13+
default=DEFAULT_ORIGIN,
14+
description=DocStrings.HARDWARE_LISTING_ORIGIN_DESCRIPTION,
15+
),
16+
]
17+
tree_name: str = Field(description=DocStrings.TREE_NAME_PATH_DESCRIPTION)
18+
git_repository_url: str = Field(
19+
description=DocStrings.TREE_QUERY_GIT_URL_DESCRIPTION
20+
)
21+
git_repository_branch: str = Field(
22+
description=DocStrings.DEFAULT_GIT_BRANCH_DESCRIPTION
23+
)
24+
git_commit_hash: str = Field(description=DocStrings.COMMIT_HASH_PATH_DESCRIPTION)
25+
26+
27+
class HardwareListingByRevisionQueryParams(BaseModel):
28+
origin: Annotated[
29+
str,
30+
Field(default=DEFAULT_ORIGIN),
31+
BeforeValidator(lambda o: DEFAULT_ORIGIN if o is None else o),
32+
]
33+
tree_name: str
34+
git_repository_url: str
35+
git_repository_branch: str
36+
git_commit_hash: str
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
from datetime import datetime
2+
from typing import Annotated
3+
4+
from pydantic import BaseModel, BeforeValidator, Field
5+
6+
from kernelCI_app.constants.general import DEFAULT_ORIGIN
7+
from kernelCI_app.constants.localization import DocStrings
8+
9+
10+
class HardwareSelectorRevision(BaseModel):
11+
git_commit_hash: str
12+
git_commit_name: str | None = None
13+
start_time: datetime
14+
15+
16+
class HardwareSelectorBranch(BaseModel):
17+
git_repository_url: str
18+
git_repository_branch: str
19+
revisions: list[HardwareSelectorRevision]
20+
21+
22+
class HardwareSelectorTree(BaseModel):
23+
tree_name: str
24+
branches: list[HardwareSelectorBranch]
25+
26+
27+
class HardwareSelectorsResponse(BaseModel):
28+
trees: list[HardwareSelectorTree]
29+
30+
31+
class HardwareSelectorsQueryParamsDocumentationOnly(BaseModel):
32+
origin: Annotated[
33+
str,
34+
Field(
35+
default=DEFAULT_ORIGIN,
36+
description=DocStrings.HARDWARE_LISTING_ORIGIN_DESCRIPTION,
37+
),
38+
]
39+
40+
41+
class HardwareSelectorsQueryParams(BaseModel):
42+
origin: Annotated[
43+
str,
44+
Field(default=DEFAULT_ORIGIN),
45+
BeforeValidator(lambda o: DEFAULT_ORIGIN if o is None else o),
46+
]

backend/kernelCI_app/urls.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,11 @@ def view_cache(view):
115115
name="testIssues",
116116
),
117117
path("log-downloader/", view_cache(views.LogDownloaderView), name="logDownloader"),
118+
path(
119+
"hardware/selectors/",
120+
view_cache(views.HardwareSelectorsView),
121+
name="hardwareSelectors",
122+
),
118123
path(
119124
"hardware/<str:hardware_id>",
120125
view_cache(views.HardwareDetails),
@@ -146,6 +151,11 @@ def view_cache(view):
146151
name="hardwareDetailsTests",
147152
),
148153
path("hardware/", view_cache(views.HardwareView), name="hardware"),
154+
path(
155+
"hardware-by-revision/",
156+
view_cache(views.HardwareByRevisionView),
157+
name="hardwareByRevision",
158+
),
149159
path("hardware-v2/", view_cache(views.HardwareViewV2), name="hardware-v2"),
150160
path("issue/", view_cache(views.IssueView), name="issue"),
151161
path(
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
from http import HTTPStatus
2+
3+
from drf_spectacular.utils import extend_schema
4+
from pydantic import ValidationError
5+
from rest_framework.request import Request
6+
from rest_framework.response import Response
7+
from rest_framework.views import APIView
8+
9+
from kernelCI_app.queries.hardware import get_hardware_listing_data_by_revision
10+
from kernelCI_app.typeModels.hardwareListing import (
11+
HardwareItem,
12+
HardwareListingResponse,
13+
)
14+
from kernelCI_app.typeModels.hardwareListingByRevision import (
15+
HardwareListingByRevisionQueryParams,
16+
HardwareListingByRevisionQueryParamsDocumentationOnly,
17+
)
18+
19+
20+
class HardwareByRevisionView(APIView):
21+
def _sanitize_records(self, hardwares_raw: list[dict]) -> list[HardwareItem]:
22+
hardwares = []
23+
for hardware in hardwares_raw:
24+
hardwares.append(
25+
HardwareItem(
26+
platform=hardware["platform"],
27+
hardware=hardware["hardware"],
28+
build_status_summary={
29+
"PASS": hardware["pass_builds"],
30+
"FAIL": hardware["fail_builds"],
31+
"NULL": hardware["null_builds"],
32+
"ERROR": hardware["error_builds"],
33+
"MISS": hardware["miss_builds"],
34+
"DONE": hardware["done_builds"],
35+
"SKIP": hardware["skip_builds"],
36+
},
37+
boot_status_summary={
38+
"PASS": hardware["pass_boots"],
39+
"FAIL": hardware["fail_boots"],
40+
"NULL": hardware["null_boots"],
41+
"ERROR": hardware["error_boots"],
42+
"MISS": hardware["miss_boots"],
43+
"DONE": hardware["done_boots"],
44+
"SKIP": hardware["skip_boots"],
45+
},
46+
test_status_summary={
47+
"PASS": hardware["pass_tests"],
48+
"FAIL": hardware["fail_tests"],
49+
"NULL": hardware["null_tests"],
50+
"ERROR": hardware["error_tests"],
51+
"MISS": hardware["miss_tests"],
52+
"DONE": hardware["done_tests"],
53+
"SKIP": hardware["skip_tests"],
54+
},
55+
)
56+
)
57+
58+
return hardwares
59+
60+
@extend_schema(
61+
parameters=[HardwareListingByRevisionQueryParamsDocumentationOnly],
62+
responses=HardwareListingResponse,
63+
)
64+
def get(self, request: Request):
65+
try:
66+
query_params = HardwareListingByRevisionQueryParams(
67+
origin=request.GET.get("origin"),
68+
tree_name=request.GET.get("tree_name"),
69+
git_repository_url=request.GET.get("git_repository_url"),
70+
git_repository_branch=request.GET.get("git_repository_branch"),
71+
git_commit_hash=request.GET.get("git_commit_hash"),
72+
)
73+
except ValidationError as e:
74+
return Response(data=e.json(), status=HTTPStatus.BAD_REQUEST)
75+
76+
hardwares_raw = get_hardware_listing_data_by_revision(
77+
origin=query_params.origin,
78+
tree_name=query_params.tree_name,
79+
git_repository_url=query_params.git_repository_url,
80+
git_repository_branch=query_params.git_repository_branch,
81+
git_commit_hash=query_params.git_commit_hash,
82+
)
83+
84+
try:
85+
sanitized_records = self._sanitize_records(hardwares_raw=hardwares_raw)
86+
result = HardwareListingResponse(hardware=sanitized_records)
87+
except ValidationError as e:
88+
return Response(data=e.json(), status=HTTPStatus.INTERNAL_SERVER_ERROR)
89+
90+
return Response(data=result.model_dump(), status=HTTPStatus.OK)

0 commit comments

Comments
 (0)