Skip to content

Commit a6746a6

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

8 files changed

Lines changed: 573 additions & 1 deletion

File tree

backend/kernelCI_app/queries/hardware.py

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

203203

204+
def get_hardware_selectors(origin: str) -> list[dict]:
205+
params = {"origin": origin}
206+
207+
query = """
208+
WITH qualified_revisions AS (
209+
SELECT
210+
c.tree_name,
211+
c.git_repository_url,
212+
c.git_repository_branch,
213+
c.git_commit_hash,
214+
c.git_commit_name,
215+
MAX(c.start_time) AS latest_start_time
216+
FROM
217+
checkouts c
218+
INNER JOIN builds b ON b.checkout_id = c.id
219+
INNER JOIN tests ON tests.build_id = b.id
220+
WHERE
221+
tests.origin = %(origin)s
222+
AND tests.environment_misc ->> 'platform' IS NOT NULL
223+
GROUP BY
224+
c.tree_name,
225+
c.git_repository_url,
226+
c.git_repository_branch,
227+
c.git_commit_hash,
228+
c.git_commit_name
229+
)
230+
SELECT
231+
qr.tree_name,
232+
qr.git_repository_url,
233+
qr.git_repository_branch,
234+
qr.git_commit_hash,
235+
qr.git_commit_name,
236+
qr.latest_start_time,
237+
MAX(qr.latest_start_time) OVER (
238+
PARTITION BY
239+
qr.tree_name,
240+
qr.git_repository_url,
241+
qr.git_repository_branch
242+
) AS branch_latest_start_time
243+
FROM
244+
qualified_revisions qr
245+
ORDER BY
246+
qr.tree_name ASC,
247+
branch_latest_start_time DESC,
248+
qr.latest_start_time DESC
249+
"""
250+
251+
with connection.cursor() as cursor:
252+
cursor.execute(query, params)
253+
return dict_fetchall(cursor)
254+
255+
256+
def get_hardware_listing_data_by_revision(
257+
*,
258+
origin: str,
259+
tree_name: str,
260+
git_repository_url: str,
261+
git_repository_branch: str,
262+
git_commit_hash: str,
263+
) -> list[dict]:
264+
count_clauses = _get_hardware_listing_count_clauses()
265+
params = {
266+
"origin": origin,
267+
"tree_name": tree_name,
268+
"git_repository_url": git_repository_url,
269+
"git_repository_branch": git_repository_branch,
270+
"git_commit_hash": git_commit_hash,
271+
}
272+
273+
query = f"""
274+
WITH relevant_tests AS (
275+
SELECT
276+
tests.environment_compatible,
277+
tests.environment_misc ->> 'platform' AS platform,
278+
tests.status,
279+
tests.path,
280+
tests.id,
281+
b.id AS build_id,
282+
b.status AS build_status
283+
FROM
284+
checkouts c
285+
INNER JOIN builds b ON b.checkout_id = c.id
286+
INNER JOIN tests ON tests.build_id = b.id
287+
WHERE
288+
c.tree_name = %(tree_name)s
289+
AND c.git_repository_url = %(git_repository_url)s
290+
AND c.git_repository_branch = %(git_repository_branch)s
291+
AND c.git_commit_hash = %(git_commit_hash)s
292+
AND tests.origin = %(origin)s
293+
AND tests.environment_misc ->> 'platform' IS NOT NULL
294+
),
295+
compatible_values AS (
296+
SELECT
297+
platform,
298+
UNNEST(environment_compatible) AS compatible
299+
FROM
300+
relevant_tests
301+
WHERE
302+
environment_compatible IS NOT NULL
303+
),
304+
compatible_agg AS (
305+
SELECT
306+
platform,
307+
ARRAY_AGG(DISTINCT compatible ORDER BY compatible) AS hardware
308+
FROM
309+
compatible_values
310+
GROUP BY
311+
platform
312+
)
313+
SELECT
314+
relevant_tests.platform,
315+
compatible_agg.hardware,
316+
{count_clauses}
317+
FROM
318+
relevant_tests
319+
LEFT JOIN compatible_agg ON compatible_agg.platform = relevant_tests.platform
320+
GROUP BY
321+
relevant_tests.platform,
322+
compatible_agg.hardware
323+
ORDER BY
324+
relevant_tests.platform ASC
325+
"""
326+
327+
with connection.cursor() as cursor:
328+
cursor.execute(query, params)
329+
return dict_fetchall(cursor)
330+
331+
204332
def get_hardware_listing_data_bulk(
205333
keys: list[tuple[str, str]],
206334
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: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
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+
latest_start_time: datetime
20+
revisions: list[HardwareSelectorRevision]
21+
22+
23+
class HardwareSelectorTree(BaseModel):
24+
tree_name: str
25+
latest_start_time: datetime
26+
branches: list[HardwareSelectorBranch]
27+
28+
29+
class HardwareSelectorsResponse(BaseModel):
30+
trees: list[HardwareSelectorTree]
31+
32+
33+
class HardwareSelectorsQueryParamsDocumentationOnly(BaseModel):
34+
origin: Annotated[
35+
str,
36+
Field(
37+
default=DEFAULT_ORIGIN,
38+
description=DocStrings.HARDWARE_LISTING_ORIGIN_DESCRIPTION,
39+
),
40+
]
41+
42+
43+
class HardwareSelectorsQueryParams(BaseModel):
44+
origin: Annotated[
45+
str,
46+
Field(default=DEFAULT_ORIGIN),
47+
BeforeValidator(lambda o: DEFAULT_ORIGIN if o is None else o),
48+
]

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)