Skip to content

Commit

Permalink
refactor: lazy load test status history
Browse files Browse the repository at this point in the history
- Separates status history from testDetails endpoint; - Disables testIssues frontend fetch until testDetails page is loaded; - Changes typing as necessary

Closes #1051
  • Loading branch information
MarceloRobert committed Mar 10, 2025
1 parent 7d38a29 commit a013a04
Show file tree
Hide file tree
Showing 10 changed files with 317 additions and 131 deletions.
13 changes: 6 additions & 7 deletions backend/kernelCI_app/queries/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ def get_test_details_data(*, test_id):
)


# TODO: combine with the test_details query
def get_test_status_history(
*,
path: str,
Expand All @@ -43,17 +42,17 @@ def get_test_status_history(
platform: Optional[str],
current_test_timestamp: datetime,
):
query = Tests.objects.values(
"field_timestamp",
"id",
"status",
"build__checkout__git_commit_hash",
).filter(
query = Tests.objects.filter(
path=path,
build__checkout__origin=origin,
build__checkout__git_repository_url=git_repository_url,
build__checkout__git_repository_branch=git_repository_branch,
field_timestamp__lte=current_test_timestamp,
).values(
"field_timestamp",
"id",
"status",
"build__checkout__git_commit_hash",
)

if platform is None:
Expand Down
40 changes: 28 additions & 12 deletions backend/kernelCI_app/typeModels/testDetails.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from typing import Literal
from typing import Literal, Optional
from pydantic import BaseModel, Field

from kernelCI_app.typeModels.databases import (
Origin,
Test__Id,
Build__Id,
Test__Status,
Expand All @@ -24,17 +25,6 @@
Timestamp,
)

type PossibleRegressionType = Literal["regression", "fixed", "unstable", "pass", "fail"]


class TestStatusHistoryItem(BaseModel):
field_timestamp: Timestamp
id: Test__Id
status: Test__Status
git_commit_hash: Checkout__GitCommitHash = Field(
validation_alias="build__checkout__git_commit_hash"
)


class TestDetailsResponse(BaseModel):
id: Test__Id
Expand Down Expand Up @@ -64,5 +54,31 @@ class TestDetailsResponse(BaseModel):
validation_alias="build__checkout__git_commit_tags"
)
tree_name: Checkout__TreeName = Field(validation_alias="build__checkout__tree_name")
origin: Origin = Field(validation_alias="build__checkout__origin")
field_timestamp: Timestamp


type PossibleRegressionType = Literal["regression", "fixed", "unstable", "pass", "fail"]


class TestStatusHistoryItem(BaseModel):
field_timestamp: Timestamp
id: Test__Id
status: Test__Status
git_commit_hash: Checkout__GitCommitHash = Field(
validation_alias="build__checkout__git_commit_hash"
)


class TestStatusHistoryResponse(BaseModel):
status_history: list[TestStatusHistoryItem]
regression_type: PossibleRegressionType


class TestStatusHistoryRequest(BaseModel):
path: Test__Path
origin: Origin
git_repository_url: Checkout__GitRepositoryUrl
git_repository_branch: Checkout__GitRepositoryBranch
platform: Optional[str] = None
current_test_timestamp: Timestamp
5 changes: 5 additions & 0 deletions backend/kernelCI_app/unitTests/utils/fields/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@
"environment_misc",
"tree_name",
"git_repository_branch",
"origin",
"field_timestamp",
]

status_history_expected_fields = [
"status_history",
"regression_type",
]
5 changes: 5 additions & 0 deletions backend/kernelCI_app/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ def view_cache(view):


urlpatterns = [
path(
"test/status-history",
view_cache(views.TestStatusHistory),
name="testStatusHistory",
),
path("test/<str:test_id>", view_cache(views.TestDetails), name="testDetails"),
path("tree/", view_cache(views.TreeView), name="tree"),
path("tree-fast/", view_cache(views.TreeViewFast), name="tree-fast"),
Expand Down
67 changes: 2 additions & 65 deletions backend/kernelCI_app/views/testDetailsView.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
from http import HTTPStatus
from typing import Optional
from kernelCI_app.helpers.errorHandling import create_api_error_response
from kernelCI_app.queries.test import get_test_details_data, get_test_status_history
from kernelCI_app.typeModels.databases import FAIL_STATUS, PASS_STATUS
from kernelCI_app.queries.test import get_test_details_data
from kernelCI_app.typeModels.testDetails import (
PossibleRegressionType,
TestDetailsResponse,
)
from drf_spectacular.utils import extend_schema
Expand All @@ -14,79 +11,19 @@


class TestDetails(APIView):
# TODO: create unit tests for this method
def process_test_status_history(
self, *, status_history: list[dict]
) -> PossibleRegressionType:
history_task: PossibleRegressionType
first_test_flag = True
status_changed = False

for test in status_history:
test_status = test["status"]
if first_test_flag:
if test_status == PASS_STATUS:
history_task = "pass"
starting_status = PASS_STATUS
opposite_status = FAIL_STATUS
elif test_status == FAIL_STATUS:
history_task = "fail"
starting_status = FAIL_STATUS
opposite_status = PASS_STATUS
else:
return "unstable"
first_test_flag = False
continue

is_inconclusive = test_status != PASS_STATUS and test_status != FAIL_STATUS

if test_status == opposite_status:
status_changed = True
if history_task == "pass":
history_task = "fixed"
elif history_task == "fail":
history_task = "regression"
if (status_changed and test_status == starting_status) or is_inconclusive:
return "unstable"

return history_task

@extend_schema(
responses=TestDetailsResponse,
)
def get(self, _request, test_id: str) -> Response:

response = get_test_details_data(test_id=test_id)

if response is None:
return create_api_error_response(
error_message="Test not found", status_code=HTTPStatus.OK
)

environment_misc = response.get("environment_misc")
platform: Optional[str] = None
if environment_misc is not None:
platform = environment_misc.get("platform")

status_history_response = get_test_status_history(
path=response["path"],
origin=response["build__checkout__origin"],
git_repository_url=response["build__checkout__git_repository_url"],
git_repository_branch=response["build__checkout__git_repository_branch"],
platform=platform,
current_test_timestamp=response["field_timestamp"],
)

regression_type = self.process_test_status_history(
status_history=status_history_response
)

try:
valid_response = TestDetailsResponse(
**response,
status_history=status_history_response,
regression_type=regression_type,
)
valid_response = TestDetailsResponse(**response)
except ValidationError as e:
return Response(data=e.json(), status=HTTPStatus.INTERNAL_SERVER_ERROR)

Expand Down
93 changes: 93 additions & 0 deletions backend/kernelCI_app/views/testStatusHistoryView.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
from http import HTTPStatus

from django.http import HttpRequest
from kernelCI_app.queries.test import get_test_status_history
from kernelCI_app.typeModels.databases import FAIL_STATUS, PASS_STATUS
from kernelCI_app.typeModels.testDetails import (
PossibleRegressionType,
TestStatusHistoryRequest,
TestStatusHistoryResponse,
)
from drf_spectacular.utils import extend_schema
from rest_framework.views import APIView
from rest_framework.response import Response
from pydantic import ValidationError


class TestStatusHistory(APIView):
# TODO: create unit tests for this method
def process_test_status_history(
self, *, status_history: list[dict]
) -> PossibleRegressionType:
history_task: PossibleRegressionType
first_test_flag = True
status_changed = False

for test in status_history:
test_status = test["status"]
if first_test_flag:
if test_status == PASS_STATUS:
history_task = "pass"
starting_status = PASS_STATUS
opposite_status = FAIL_STATUS
elif test_status == FAIL_STATUS:
history_task = "fail"
starting_status = FAIL_STATUS
opposite_status = PASS_STATUS
else:
return "unstable"
first_test_flag = False
continue

is_inconclusive = test_status != PASS_STATUS and test_status != FAIL_STATUS

if test_status == opposite_status:
status_changed = True
if history_task == "pass":
history_task = "fixed"
elif history_task == "fail":
history_task = "regression"
if (status_changed and test_status == starting_status) or is_inconclusive:
return "unstable"

return history_task

@extend_schema(
request=TestStatusHistoryRequest,
responses=TestStatusHistoryResponse,
)
def get(self, request: HttpRequest) -> Response:
try:
params = TestStatusHistoryRequest(
path=request.GET.get("path"),
origin=request.GET.get("origin"),
git_repository_branch=request.GET.get("git_repository_branch"),
git_repository_url=request.GET.get("git_repository_url"),
platform=request.GET.get("platform"),
current_test_timestamp=request.GET.get("current_test_timestamp"),
)
except ValidationError as e:
return Response(data=e.json(), status=HTTPStatus.BAD_REQUEST)

status_history_response = get_test_status_history(
path=params.path,
origin=params.origin,
git_repository_url=params.git_repository_url,
git_repository_branch=params.git_repository_branch,
platform=params.platform,
current_test_timestamp=params.current_test_timestamp,
)

regression_type = self.process_test_status_history(
status_history=status_history_response
)

try:
valid_response = TestStatusHistoryResponse(
status_history=status_history_response,
regression_type=regression_type,
)
except ValidationError as e:
return Response(data=e.json(), status=HTTPStatus.INTERNAL_SERVER_ERROR)

return Response(valid_response.model_dump())
50 changes: 40 additions & 10 deletions backend/schema.yml
Original file line number Diff line number Diff line change
Expand Up @@ -688,6 +688,22 @@ paths:
schema:
$ref: '#/components/schemas/DetailsIssuesResponse'
description: ''
/api/test/status-history:
get:
operationId: test_status_history_retrieve
tags:
- test
security:
- cookieAuth: []
- basicAuth: []
- {}
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/TestStatusHistoryResponse'
description: ''
/api/tree/:
get:
operationId: tree_retrieve
Expand Down Expand Up @@ -2638,13 +2654,10 @@ components:
$ref: '#/components/schemas/Checkout__GitCommitTags'
tree_name:
$ref: '#/components/schemas/Checkout__TreeName'
status_history:
items:
$ref: '#/components/schemas/TestStatusHistoryItem'
title: Status History
type: array
regression_type:
$ref: '#/components/schemas/PossibleRegressionType'
origin:
$ref: '#/components/schemas/Origin'
field_timestamp:
$ref: '#/components/schemas/Timestamp'
required:
- id
- build_id
Expand All @@ -2665,8 +2678,8 @@ components:
- git_repository_url
- git_commit_tags
- tree_name
- status_history
- regression_type
- origin
- field_timestamp
title: TestDetailsResponse
type: object
TestHistoryItem:
Expand Down Expand Up @@ -2800,6 +2813,20 @@ components:
- git_commit_hash
title: TestStatusHistoryItem
type: object
TestStatusHistoryResponse:
properties:
status_history:
items:
$ref: '#/components/schemas/TestStatusHistoryItem'
title: Status History
type: array
regression_type:
$ref: '#/components/schemas/PossibleRegressionType'
required:
- status_history
- regression_type
title: TestStatusHistoryResponse
type: object
TestSummary:
properties:
status:
Expand Down Expand Up @@ -2874,7 +2901,10 @@ components:
- type: 'null'
Test__EnvironmentMisc:
anyOf:
- $ref: '#/components/schemas/EnvironmentMisc'
- type: object
- items:
type: object
type: array
- type: 'null'
Test__Id:
type: string
Expand Down
Loading

0 comments on commit a013a04

Please sign in to comment.