Skip to content

Commit

Permalink
feat: add IssueListing endpoint
Browse files Browse the repository at this point in the history
Part of #938

Closes #963
  • Loading branch information
MarceloRobert committed Feb 20, 2025
1 parent ae278c0 commit 5f896f6
Show file tree
Hide file tree
Showing 14 changed files with 337 additions and 55 deletions.
1 change: 1 addition & 0 deletions backend/kernelCI_app/constants/general.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
DEFAULT_ORIGIN = "maestro"
DEFAULT_INTERVAL_IN_DAYS = 7

UNKNOWN_STRING = "Unknown"
UNCATEGORIZED_STRING = "Uncategorized"
Expand Down
13 changes: 0 additions & 13 deletions backend/kernelCI_app/helpers/date.py

This file was deleted.

5 changes: 2 additions & 3 deletions backend/kernelCI_app/helpers/errorHandling.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,9 @@ def __init__(self, message, status_code=400):
Exception.__init__(self)
self.message = message
self.status_code = status_code
self.json_response = JsonResponse({"error": message}, status=status_code)

def getJsonResponse(self):
return self.json_response
def getRestResponse(self):
return Response(data={"error": self.message}, status=self.status_code)


@typing_extensions.deprecated(
Expand Down
15 changes: 15 additions & 0 deletions backend/kernelCI_app/typeModels/commonListing.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from typing import Annotated
from pydantic import BaseModel, BeforeValidator, Field

from kernelCI_app.constants.general import DEFAULT_INTERVAL_IN_DAYS, DEFAULT_ORIGIN


class ListingQueryParameters(BaseModel):
origin: Annotated[
str, BeforeValidator(lambda o: DEFAULT_ORIGIN if o is None else o)
]
interval_in_days: Annotated[
int,
Field(gt=0),
BeforeValidator(lambda i: DEFAULT_INTERVAL_IN_DAYS if i is None else i),
]
12 changes: 8 additions & 4 deletions backend/kernelCI_app/typeModels/hardwareListing.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from datetime import datetime
from pydantic import BaseModel
from typing import List, TypedDict, Union, Set
from pydantic import BaseModel, BeforeValidator
from typing import Annotated, List, TypedDict, Union, Set

from kernelCI_app.constants.general import DEFAULT_ORIGIN

Expand All @@ -27,12 +27,16 @@ class HardwareResponse(BaseModel):
# documentation purposes. This model is not used in the code.
# TODO Remove timestamp from the api and this model
class HardwareQueryParamsDocumentationOnly(BaseModel):
origin: str = DEFAULT_ORIGIN
origin: Annotated[
str, BeforeValidator(lambda o: DEFAULT_ORIGIN if o is None else o)
]
startTimestampInSeconds: str
endTimeStampInSeconds: str


class HardwareQueryParams(BaseModel):
origin: str = DEFAULT_ORIGIN
origin: Annotated[
str, BeforeValidator(lambda o: DEFAULT_ORIGIN if o is None else o)
]
start_date: datetime
end_date: datetime
27 changes: 27 additions & 0 deletions backend/kernelCI_app/typeModels/issueListing.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from pydantic import BaseModel

from kernelCI_app.typeModels.databases import (
Issue__Comment,
Issue__CulpritCode,
Issue__CulpritHarness,
Issue__CulpritTool,
Issue__Id,
Issue__Version,
Timestamp,
)
from kernelCI_app.typeModels.issues import FirstIncident


class IssueListingItem(BaseModel):
field_timestamp: Timestamp
id: Issue__Id
comment: Issue__Comment
version: Issue__Version
culprit_code: Issue__CulpritCode
culprit_tool: Issue__CulpritTool
culprit_harness: Issue__CulpritHarness


class IssueListingResponse(BaseModel):
issues: list[IssueListingItem]
extras: dict[str, FirstIncident]
5 changes: 0 additions & 5 deletions backend/kernelCI_app/typeModels/treeListing.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,3 @@ class TreeListingResponse(RootModel):

class TreeListingFastResponse(RootModel):
root: List[CheckoutFast]


class TreeListingQueryParameters(BaseModel):
origin: str
intervalInDays: int
3 changes: 3 additions & 0 deletions backend/kernelCI_app/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,9 @@ def viewCache(view):
path("hardware/",
viewCache(views.HardwareView),
name="hardware"),
path("issue/",
viewCache(views.IssueView),
name="issue"),
path("issue/extras/",
viewCache(views.IssueExtraDetails),
name="issueExtraDetails"
Expand Down
9 changes: 5 additions & 4 deletions backend/kernelCI_app/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@
from django.utils import timezone
from datetime import timedelta

from kernelCI_app.constants.general import DEFAULT_INTERVAL_IN_DAYS
from kernelCI_app.helpers.logger import log_message
from kernelCI_app.typeModels.issues import IncidentInfo, Issue, IssueDict

DEFAULT_QUERY_TIME_INTERVAL = {"days": 7}
DEFAULT_QUERY_TIME_INTERVAL = {"days": DEFAULT_INTERVAL_IN_DAYS}


def create_issue(
Expand All @@ -27,8 +28,8 @@ def create_issue(


@typing_extensions.deprecated(
'The `convert_issues_dict_to_list` method is deprecated; use `convert_issues_dict_to_list_typed` '
'and use type validation.',
"The `convert_issues_dict_to_list` method is deprecated; use `convert_issues_dict_to_list_typed` "
"and use type validation.",
)
def convert_issues_dict_to_list(issues_dict: Dict[str, Issue]) -> List[Issue]:
return list(issues_dict.values())
Expand Down Expand Up @@ -77,7 +78,7 @@ def getErrorResponseBody(reason: str) -> bytes:


def string_to_json(string: str) -> Optional[dict]:
if (string):
if string:
try:
return json.loads(string)
except json.JSONDecodeError as e:
Expand Down
95 changes: 95 additions & 0 deletions backend/kernelCI_app/views/issueView.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
from datetime import datetime, timedelta, timezone
from http import HTTPStatus
from drf_spectacular.utils import extend_schema
from rest_framework.views import APIView
from rest_framework.response import Response
from pydantic import ValidationError

from kernelCI_app.helpers.errorHandling import (
create_api_error_response,
)
from kernelCI_app.helpers.issueExtras import assign_issue_first_seen
from kernelCI_app.models import Issues
from kernelCI_app.typeModels.commonListing import ListingQueryParameters
from kernelCI_app.typeModels.issueListing import (
IssueListingResponse,
)
from kernelCI_app.typeModels.issues import FirstIncident, ProcessedExtraDetailedIssues


def get_issue_listing_data(origin: str, interval_date):
issues_records = Issues.objects.values(
"id",
"field_timestamp",
"comment",
"version",
"culprit_code",
"culprit_harness",
"culprit_tool",
).filter(
origin=origin,
field_timestamp__gte=interval_date,
)
return issues_records


class IssueView(APIView):
def __init__(self):
self.issue_records: list[dict[str]] = []
self.processed_extra_issue_details: ProcessedExtraDetailedIssues = {}
self.first_incidents: dict[str, FirstIncident] = {}

def _format_processing_for_response(self) -> None:
for (
issue_extras_id,
issue_extras_data,
) in self.processed_extra_issue_details.items():
self.first_incidents[issue_extras_id] = issue_extras_data["first_incident"]

@extend_schema(
parameters=[ListingQueryParameters],
responses=IssueListingResponse,
methods=["GET"],
)
def get(self, _request) -> Response:
try:
request_params = ListingQueryParameters(
origin=(_request.GET.get("origin")),
interval_in_days=_request.GET.get("intervalInDays"),
)
except ValidationError as e:
return Response(data=e.json(), status=HTTPStatus.BAD_REQUEST)

origin_param = request_params.origin
interval_in_days = request_params.interval_in_days

interval_date = datetime.now(timezone.utc) - timedelta(days=interval_in_days)
interval_param = interval_date.replace(
hour=0, minute=0, second=0, microsecond=0
)

self.issue_records = get_issue_listing_data(
origin=origin_param, interval_date=interval_param
)

if len(self.issue_records) == 0:
return create_api_error_response(
error_message="No issues found", status_code=HTTPStatus.OK
)

issue_key_list = [
(issue["id"], issue["version"]) for issue in self.issue_records
]
assign_issue_first_seen(
issue_key_list=issue_key_list,
processed_issues_table=self.processed_extra_issue_details,
)

try:
self._format_processing_for_response()
valid_data = IssueListingResponse(
issues=self.issue_records, extras=self.first_incidents
)
except ValidationError as e:
return Response(data=e.json(), status=HTTPStatus.INTERNAL_SERVER_ERROR)
return Response(data=valid_data.model_dump())
24 changes: 11 additions & 13 deletions backend/kernelCI_app/views/treeView.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,19 @@
from drf_spectacular.utils import extend_schema
from rest_framework.views import APIView
from rest_framework.response import Response
from kernelCI_app.typeModels.commonListing import ListingQueryParameters
from kernelCI_app.utils import getQueryTimeInterval
from kernelCI_app.helpers.errorHandling import (
ExceptionWithJsonResponse,
create_api_error_response,
)
from kernelCI_app.helpers.date import parseIntervalInDaysGetParameter
from http import HTTPStatus
from kernelCI_app.typeModels.treeListing import (
TreeListingResponse,
TreeListingQueryParameters,
)
from pydantic import ValidationError
from django.db import connection


DEFAULT_ORIGIN = "maestro"


class TreeView(APIView):
def _sanitize_tree(self, checkout: Dict) -> Dict:
build_status = {
Expand Down Expand Up @@ -63,17 +58,20 @@ def _sanitize_tree(self, checkout: Dict) -> Dict:

@extend_schema(
responses=TreeListingResponse,
parameters=[TreeListingQueryParameters],
methods=["GET"],
parameters=[ListingQueryParameters],
methods=["GET"]
)
def get(self, request) -> Response:
origin_param = request.GET.get("origin", DEFAULT_ORIGIN)
try:
interval_days = parseIntervalInDaysGetParameter(
request.GET.get("intervalInDays", 3)
request_params = ListingQueryParameters(
origin=request.GET.get("origin"),
interval_in_days=request.GET.get("intervalInDays")
)
except ExceptionWithJsonResponse as e:
return e.getJsonResponse()
except ValidationError as e:
return Response(data=e.json(), status=HTTPStatus.BAD_REQUEST)

origin_param = request_params.origin
interval_days = request_params.interval_in_days

interval_days_data = {"days": interval_days}

Expand Down
21 changes: 14 additions & 7 deletions backend/kernelCI_app/views/treeViewFast.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,34 @@
from rest_framework.response import Response
from drf_spectacular.utils import extend_schema
from kernelCI_app.models import Checkouts
from kernelCI_app.typeModels.commonListing import ListingQueryParameters
from kernelCI_app.utils import getQueryTimeInterval
from http import HTTPStatus
from kernelCI_app.helpers.errorHandling import create_api_error_response
from kernelCI_app.typeModels.treeListing import (
TreeListingFastResponse,
TreeListingQueryParameters
)
from pydantic import ValidationError

DEFAULT_ORIGIN = "maestro"


class TreeViewFast(APIView):
@extend_schema(
responses=TreeListingFastResponse,
parameters=[TreeListingQueryParameters],
methods=["GET"]
parameters=[ListingQueryParameters],
methods=["GET"],
)
def get(self, request):
origin = request.GET.get("origin", DEFAULT_ORIGIN)
interval_days = int(request.GET.get("intervalInDays", "7"))
try:
request_params = ListingQueryParameters(
origin=(request.GET.get("origin")),
interval_in_days=request.GET.get("intervalInDays"),
)
except ValidationError as e:
return Response(data=e.json(), status=HTTPStatus.BAD_REQUEST)

origin = request_params.origin
interval_days = request_params.interval_in_days

interval_days_data = {"days": interval_days}

checkouts = Checkouts.objects.raw(
Expand Down
Loading

0 comments on commit 5f896f6

Please sign in to comment.