diff --git a/backend/kernelCI_app/constants/general.py b/backend/kernelCI_app/constants/general.py
index 6063d7067..aa471d650 100644
--- a/backend/kernelCI_app/constants/general.py
+++ b/backend/kernelCI_app/constants/general.py
@@ -1,3 +1,4 @@
DEFAULT_ORIGIN = 'maestro'
UNKNOWN_STRING = 'Unknown'
+UNCATEGORIZED_STRING = "Uncategorized"
diff --git a/backend/kernelCI_app/helpers/commonDetails.py b/backend/kernelCI_app/helpers/commonDetails.py
index 80f9b65af..a52bac421 100644
--- a/backend/kernelCI_app/helpers/commonDetails.py
+++ b/backend/kernelCI_app/helpers/commonDetails.py
@@ -1,5 +1,6 @@
-from kernelCI_app.constants.general import UNKNOWN_STRING
-from typing import Set
+from typing import Dict, Literal, Set
+
+type PossibleTabs = Literal["build", "boot", "test"]
def add_unfiltered_issue(
@@ -8,9 +9,11 @@ def add_unfiltered_issue(
issue_version: int,
should_increment: bool,
issue_set: Set,
- is_invalid: bool
+ is_invalid: bool,
+ unknown_issue_flag_dict: Dict[PossibleTabs, bool],
+ unknown_issue_flag_tab: PossibleTabs,
) -> None:
if issue_id is not None and issue_version is not None and should_increment:
issue_set.add((issue_id, issue_version))
elif is_invalid is True:
- issue_set.add((UNKNOWN_STRING, None))
+ unknown_issue_flag_dict[unknown_issue_flag_tab] = True
diff --git a/backend/kernelCI_app/helpers/filters.py b/backend/kernelCI_app/helpers/filters.py
index ac751eeb7..c0c5e58eb 100644
--- a/backend/kernelCI_app/helpers/filters.py
+++ b/backend/kernelCI_app/helpers/filters.py
@@ -1,6 +1,8 @@
-from typing import Optional, Dict, List, Tuple, TypedDict, Literal, Any, Union
+from typing import Optional, Dict, List, Set, Tuple, TypedDict, Literal, Any, Union
from django.http import HttpResponseBadRequest
import re
+from kernelCI_app.constants.general import UNCATEGORIZED_STRING
+from kernelCI_app.helpers.commonDetails import PossibleTabs
from kernelCI_app.typeModels.databases import PASS_STATUS, failure_status_list
from kernelCI_app.utils import getErrorResponseBody
from kernelCI_app.constants.general import UNKNOWN_STRING
@@ -83,9 +85,8 @@ def verify_issue_in_filter(
issue_version: Optional[int],
) -> bool:
is_unknown_issue = False
- if issue_filter_data == UNKNOWN_STRING:
- filter_issue_id = UNKNOWN_STRING
- filter_issue_version = None
+ if issue_filter_data == UNCATEGORIZED_STRING:
+ filter_issue_id = UNCATEGORIZED_STRING
is_unknown_issue = True
else:
filter_issue_id, filter_issue_version = issue_filter_data
@@ -123,7 +124,7 @@ def should_filter_test_issue(
if test_status == PASS_STATUS:
return True
- has_unknown_filter = UNKNOWN_STRING in issue_filters
+ has_uncategorized_filter = UNCATEGORIZED_STRING in issue_filters
is_exclusively_build_issue_result = is_exclusively_build_issue(
issue_id=issue_id,
@@ -131,10 +132,10 @@ def should_filter_test_issue(
incident_test_id=incident_test_id,
)
- if is_exclusively_build_issue_result and has_unknown_filter:
+ if is_exclusively_build_issue_result and has_uncategorized_filter:
return False
if is_exclusively_build_issue_result:
- issue_id = UNKNOWN_STRING
+ issue_id = UNCATEGORIZED_STRING
is_issue_filtered_out_result = is_issue_filtered_out(
issue_id=issue_id, issue_version=issue_version, issue_filters=issue_filters
@@ -158,7 +159,7 @@ def should_filter_build_issue(
if not is_build_invalid(build_valid):
return True
- has_unknown_filter = UNKNOWN_STRING in issue_filters
+ has_uncategorized_filter = UNCATEGORIZED_STRING in issue_filters
is_exclusively_test_issue_result = is_exclusively_test_issue(
issue_id=issue_id,
@@ -166,10 +167,10 @@ def should_filter_build_issue(
incident_test_id=incident_test_id,
)
- if is_exclusively_test_issue_result and has_unknown_filter:
+ if is_exclusively_test_issue_result and has_uncategorized_filter:
return False
if is_exclusively_test_issue_result:
- issue_id = UNKNOWN_STRING
+ issue_id = UNCATEGORIZED_STRING
is_issue_filtered_out_result = is_issue_filtered_out(
issue_id=issue_id, issue_version=issue_version, issue_filters=issue_filters
@@ -189,7 +190,7 @@ def should_increment_test_issue(
incident_test_id=incident_test_id,
)
if is_exclusively_build_issue_result:
- return (UNKNOWN_STRING, None, False)
+ return (UNCATEGORIZED_STRING, None, False)
is_issue_from_test_result = is_issue_from_test(
issue_id=issue_id,
@@ -213,7 +214,7 @@ def should_increment_build_issue(
incident_test_id=incident_test_id,
)
if is_exclusively_test_issue_result:
- return (UNKNOWN_STRING, None, False)
+ return (UNCATEGORIZED_STRING, None, False)
is_issue_from_build_result = is_issue_from_build(
issue_id=issue_id,
@@ -283,7 +284,11 @@ def __init__(self, data: Dict, process_body=False) -> None:
self.filterTestPath = ""
self.filterBootPath = ""
self.filterBuildValid = set()
- self.filterIssues = {"build": set(), "boot": set(), "test": set()}
+ self.filterIssues: Dict[PossibleTabs, Set[str, Optional[int]]] = {
+ "build": set(),
+ "boot": set(),
+ "test": set(),
+ }
self.filterPlatforms = {
"boot": set(),
"test": set(),
@@ -372,8 +377,8 @@ def _handle_issues(self, current_filter: ParsedFilter) -> None:
tab = current_filter["field"].split(".")[0]
filter_value = current_filter["value"]
- if filter_value == UNKNOWN_STRING:
- self.filterIssues[tab].add((UNKNOWN_STRING, None))
+ if filter_value == UNCATEGORIZED_STRING:
+ self.filterIssues[tab].add((UNCATEGORIZED_STRING, None))
else:
issue_id, issue_version = filter_value.rsplit(",", 1)
issue_version = int(issue_version) if issue_version != "null" else None
diff --git a/backend/kernelCI_app/helpers/hardwareDetails.py b/backend/kernelCI_app/helpers/hardwareDetails.py
index 55ff43bc7..fed3c68c7 100644
--- a/backend/kernelCI_app/helpers/hardwareDetails.py
+++ b/backend/kernelCI_app/helpers/hardwareDetails.py
@@ -4,12 +4,13 @@
from typing import Dict, List, Literal, Optional, Set
from kernelCI_app.cache import getQueryCache, setQueryCache
+from kernelCI_app.constants.general import UNCATEGORIZED_STRING
from kernelCI_app.constants.hardwareDetails import (
SELECTED_HEAD_TREE_VALUE,
)
from kernelCI_app.constants.general import UNKNOWN_STRING
from kernelCI_app.helpers.build import build_status_map
-from kernelCI_app.helpers.commonDetails import add_unfiltered_issue
+from kernelCI_app.helpers.commonDetails import PossibleTabs, add_unfiltered_issue
from kernelCI_app.helpers.filters import (
FilterParams,
is_test_failure,
@@ -892,15 +893,19 @@ def process_filters(*, instance, record: Dict) -> None:
should_increment=is_build_issue,
issue_set=instance.unfiltered_build_issues,
is_invalid=is_invalid,
+ unknown_issue_flag_dict=instance.unfiltered_uncategorized_issue_flags,
+ unknown_issue_flag_tab="build",
)
if record["id"] is not None:
if is_boot(record["path"]):
issue_set = instance.unfiltered_boot_issues
platform_set = instance.unfiltered_boot_platforms
+ flag_tab: PossibleTabs = "boot"
else:
issue_set = instance.unfiltered_test_issues
platform_set = instance.unfiltered_test_platforms
+ flag_tab: PossibleTabs = "test"
test_issue_id = record["incidents__issue__id"]
test_issue_version = record["incidents__issue__version"]
@@ -917,6 +922,8 @@ def process_filters(*, instance, record: Dict) -> None:
should_increment=is_test_issue,
issue_set=issue_set,
is_invalid=is_invalid,
+ unknown_issue_flag_dict=instance.unfiltered_uncategorized_issue_flags,
+ unknown_issue_flag_tab=flag_tab,
)
environment_misc = handle_environment_misc(record["environment_misc"])
@@ -954,9 +961,9 @@ def assign_default_record_values(record: Dict) -> None:
record["build__incidents__issue__id"] is None
and record["build__valid"] is not True
):
- record["build__incidents__issue__id"] = UNKNOWN_STRING
+ record["build__incidents__issue__id"] = UNCATEGORIZED_STRING
if record["incidents__issue__id"] is None and record["status"] == FAIL_STATUS:
- record["incidents__issue__id"] = UNKNOWN_STRING
+ record["incidents__issue__id"] = UNCATEGORIZED_STRING
def format_issue_summary_for_response(
diff --git a/backend/kernelCI_app/helpers/treeDetails.py b/backend/kernelCI_app/helpers/treeDetails.py
index 2a3179c74..12d1d1a90 100644
--- a/backend/kernelCI_app/helpers/treeDetails.py
+++ b/backend/kernelCI_app/helpers/treeDetails.py
@@ -1,6 +1,7 @@
from collections.abc import Callable
from typing import Any, Optional, TypedDict
-from kernelCI_app.helpers.commonDetails import add_unfiltered_issue
+from kernelCI_app.constants.general import UNCATEGORIZED_STRING
+from kernelCI_app.helpers.commonDetails import PossibleTabs, add_unfiltered_issue
from kernelCI_app.helpers.filters import (
is_test_failure,
should_increment_build_issue,
@@ -218,7 +219,7 @@ def get_current_row_data(current_row: dict) -> dict:
or current_row_data["build_valid"] is None
or current_row_data["test_status"] == FAIL_STATUS
):
- current_row_data["issue_id"] = UNKNOWN_STRING
+ current_row_data["issue_id"] = UNCATEGORIZED_STRING
current_row_data["build_misc"] = handle_build_misc(current_row_data["build_misc"])
if current_row_data["test_path"] is None:
current_row_data["test_path"] = UNKNOWN_STRING
@@ -545,6 +546,8 @@ def process_filters(instance, row_data: dict) -> None:
issue_version=build_issue_version,
should_increment=is_build_issue,
issue_set=instance.unfiltered_build_issues,
+ unknown_issue_flag_dict=instance.unfiltered_uncategorized_issue_flags,
+ unknown_issue_flag_tab="build",
is_invalid=is_invalid,
)
@@ -557,8 +560,10 @@ def process_filters(instance, row_data: dict) -> None:
if is_boot(row_data["test_path"]):
issue_set = instance.unfiltered_boot_issues
+ flag_tab: PossibleTabs = "boot"
else:
issue_set = instance.unfiltered_test_issues
+ flag_tab: PossibleTabs = "test"
is_invalid = row_data["test_status"] == FAIL_STATUS
add_unfiltered_issue(
@@ -567,4 +572,6 @@ def process_filters(instance, row_data: dict) -> None:
should_increment=is_test_issue,
issue_set=issue_set,
is_invalid=is_invalid,
+ unknown_issue_flag_dict=instance.unfiltered_uncategorized_issue_flags,
+ unknown_issue_flag_tab=flag_tab,
)
diff --git a/backend/kernelCI_app/typeModels/commonDetails.py b/backend/kernelCI_app/typeModels/commonDetails.py
index 409e2f40b..64dd8433c 100644
--- a/backend/kernelCI_app/typeModels/commonDetails.py
+++ b/backend/kernelCI_app/typeModels/commonDetails.py
@@ -106,6 +106,7 @@ class GlobalFilters(BaseModel):
class LocalFilters(BaseModel):
issues: List[Tuple[str, Optional[int]]]
+ has_unknown_issue: bool
class DetailsFilters(BaseModel):
diff --git a/backend/kernelCI_app/views/hardwareDetailsSummaryView.py b/backend/kernelCI_app/views/hardwareDetailsSummaryView.py
index c6279494e..b66c4ee06 100644
--- a/backend/kernelCI_app/views/hardwareDetailsSummaryView.py
+++ b/backend/kernelCI_app/views/hardwareDetailsSummaryView.py
@@ -5,6 +5,7 @@
from drf_spectacular.utils import extend_schema
from http import HTTPStatus
import json
+from kernelCI_app.helpers.commonDetails import PossibleTabs
from kernelCI_app.helpers.hardwareDetails import (
assign_default_record_values,
decide_if_is_build_in_filter,
@@ -102,6 +103,11 @@ def __init__(self):
self.unfiltered_build_issues = set()
self.unfiltered_boot_issues = set()
self.unfiltered_test_issues = set()
+ self.unfiltered_uncategorized_issue_flags: Dict[PossibleTabs, bool] = {
+ "build": False,
+ "boot": False,
+ "test": False,
+ }
self.unfiltered_boot_platforms = set()
self.unfiltered_test_platforms = set()
@@ -135,7 +141,11 @@ def _process_test(self, record: Dict) -> None:
processed_tests=self.processed_tests,
)
- if should_process_test and not is_issue_processed_result and is_test_processed_result:
+ if (
+ should_process_test
+ and not is_issue_processed_result
+ and is_test_processed_result
+ ):
process_issue(
record=record,
task_issues_dict=self.issue_dicts[test_type_key],
@@ -281,7 +291,9 @@ def post(self, request, hardware_id) -> Response:
is_all_selected = len(self.selected_commits) == 0
try:
- self._sanitize_records(records, trees_with_selected_commits, is_all_selected)
+ self._sanitize_records(
+ records, trees_with_selected_commits, is_all_selected
+ )
self._format_processing_for_response(hardware_id=hardware_id)
@@ -310,14 +322,23 @@ def post(self, request, hardware_id) -> Response:
),
builds=LocalFilters(
issues=list(self.unfiltered_build_issues),
+ has_unknown_issue=self.unfiltered_uncategorized_issue_flags[
+ "build"
+ ],
),
boots=HardwareTestLocalFilters(
issues=list(self.unfiltered_boot_issues),
platforms=list(self.unfiltered_boot_platforms),
+ has_unknown_issue=self.unfiltered_uncategorized_issue_flags[
+ "boot"
+ ],
),
tests=HardwareTestLocalFilters(
issues=list(self.unfiltered_test_issues),
platforms=list(self.unfiltered_test_platforms),
+ has_unknown_issue=self.unfiltered_uncategorized_issue_flags[
+ "test"
+ ],
),
),
common=HardwareCommon(
diff --git a/backend/kernelCI_app/views/hardwareDetailsView.py b/backend/kernelCI_app/views/hardwareDetailsView.py
index 437c80b1a..e7b99eb31 100644
--- a/backend/kernelCI_app/views/hardwareDetailsView.py
+++ b/backend/kernelCI_app/views/hardwareDetailsView.py
@@ -5,6 +5,7 @@
from django.views.decorators.csrf import csrf_exempt
from drf_spectacular.utils import extend_schema
from http import HTTPStatus
+from kernelCI_app.helpers.commonDetails import PossibleTabs
from kernelCI_app.helpers.filters import FilterParams
from kernelCI_app.helpers.hardwareDetails import (
assign_default_record_values,
@@ -256,6 +257,12 @@ def post(self, request, hardware_id):
self.builds["summary"] = create_details_build_summary(self.builds["items"])
self._format_processing_for_response()
+ self.unfiltered_uncategorized_issue_flags: Dict[PossibleTabs, bool] = {
+ "build": False,
+ "boot": False,
+ "test": False,
+ }
+
get_filter_options(
instance=self,
records=records,
@@ -307,14 +314,25 @@ def post(self, request, hardware_id):
architectures=self.global_architectures,
compilers=self.global_compilers,
),
- builds=LocalFilters(issues=list(self.unfiltered_build_issues)),
+ builds=LocalFilters(
+ issues=list(self.unfiltered_build_issues),
+ has_unknown_issue=self.unfiltered_uncategorized_issue_flags[
+ "build"
+ ],
+ ),
boots=HardwareTestLocalFilters(
issues=list(self.unfiltered_boot_issues),
platforms=list(self.unfiltered_boot_platforms),
+ has_unknown_issue=self.unfiltered_uncategorized_issue_flags[
+ "boot"
+ ],
),
tests=HardwareTestLocalFilters(
issues=list(self.unfiltered_test_issues),
platforms=list(self.unfiltered_test_platforms),
+ has_unknown_issue=self.unfiltered_uncategorized_issue_flags[
+ "test"
+ ],
),
),
common=HardwareCommon(
diff --git a/backend/kernelCI_app/views/treeDetailsSummaryView.py b/backend/kernelCI_app/views/treeDetailsSummaryView.py
index 2f61035ea..ae7a1c470 100644
--- a/backend/kernelCI_app/views/treeDetailsSummaryView.py
+++ b/backend/kernelCI_app/views/treeDetailsSummaryView.py
@@ -1,5 +1,7 @@
+from typing import Dict
from pydantic import ValidationError
from rest_framework.response import Response
+from kernelCI_app.helpers.commonDetails import PossibleTabs
from kernelCI_app.helpers.filters import FilterParams
from kernelCI_app.helpers.treeDetails import (
call_based_on_compatible_and_misc_platform,
@@ -19,7 +21,19 @@
process_tests_issue,
process_filters,
)
-from kernelCI_app.typeModels.treeDetails import SummaryResponse, TreeQueryParameters
+from kernelCI_app.typeModels.commonDetails import (
+ BuildSummary,
+ DetailsFilters,
+ GlobalFilters,
+ LocalFilters,
+ Summary,
+ TestSummary,
+)
+from kernelCI_app.typeModels.treeDetails import (
+ SummaryResponse,
+ TreeCommon,
+ TreeQueryParameters,
+)
from kernelCI_app.utils import (
convert_issues_dict_to_list,
)
@@ -74,6 +88,11 @@ def __init__(self):
self.unfiltered_test_issues = set()
self.unfiltered_boot_issues = set()
self.unfiltered_build_issues = set()
+ self.unfiltered_uncategorized_issue_flags: Dict[PossibleTabs, bool] = {
+ "build": False,
+ "boot": False,
+ "test": False,
+ }
def _process_boots_test(self, row_data):
test_id = row_data["test_id"]
@@ -168,64 +187,71 @@ def get(self, request, commit_hash: str | None):
self._sanitize_rows(rows)
- response = {
- "common": {
- "tree_url": self.tree_url,
- "hardware": list(self.hardwareUsed),
- "git_commit_tags": self.git_commit_tags,
- },
- "summary": {
- "builds": {
- "status": self.build_summary["builds"],
- "architectures": self.build_summary["architectures"],
- "configs": self.build_summary["configs"],
- "issues": self.build_issues,
- "unknown_issues": self.failed_builds_with_unknown_issues
- },
- "boots": {
- "status": self.bootStatusSummary,
- "architectures": list(self.bootArchSummary.values()),
- "configs": self.bootConfigs,
- "issues": self.bootIssues,
- "unknown_issues": self.failedBootsWithUnknownIssues,
- "environment_compatible": self.bootEnvironmentCompatible,
- "environment_misc": self.bootEnvironmentMisc,
- "fail_reasons": self.bootFailReasons,
- "failed_platforms": list(self.bootPlatformsFailing),
- },
- "tests": {
- "status": self.testStatusSummary,
- "architectures": list(self.test_arch_summary.values()),
- "configs": self.test_configs,
- "issues": self.testIssues,
- "unknown_issues": self.failedTestsWithUnknownIssues,
- "environment_compatible": self.testEnvironmentCompatible,
- "environment_misc": self.testEnvironmentMisc,
- "fail_reasons": self.testFailReasons,
- "failed_platforms": list(self.testPlatformsWithErrors),
- },
- },
- "filters": {
- "all": {
- "configs": list(self.global_configs),
- "architectures": list(self.global_architectures),
- "compilers": list(self.global_compilers),
- },
- "builds": {
- "issues": list(self.unfiltered_build_issues),
- },
- "boots": {
- "issues": list(self.unfiltered_boot_issues),
- },
- "tests": {
- "issues": list(self.unfiltered_test_issues),
- },
- },
- }
-
try:
- SummaryResponse(**response)
+ valid_response = SummaryResponse(
+ common=TreeCommon(
+ tree_url=self.tree_url,
+ hardware=list(self.hardwareUsed),
+ git_commit_tags=self.git_commit_tags,
+ ),
+ summary=Summary(
+ builds=BuildSummary(
+ status=self.build_summary["builds"],
+ architectures=self.build_summary["architectures"],
+ configs=self.build_summary["configs"],
+ issues=self.build_issues,
+ unknown_issues=self.failed_builds_with_unknown_issues,
+ ),
+ boots=TestSummary(
+ status=self.bootStatusSummary,
+ architectures=list(self.bootArchSummary.values()),
+ configs=self.bootConfigs,
+ issues=self.bootIssues,
+ unknown_issues=self.failedBootsWithUnknownIssues,
+ environment_compatible=self.bootEnvironmentCompatible,
+ environment_misc=self.bootEnvironmentMisc,
+ fail_reasons=self.bootFailReasons,
+ failed_platforms=list(self.bootPlatformsFailing),
+ ),
+ tests=TestSummary(
+ status=self.testStatusSummary,
+ architectures=list(self.test_arch_summary.values()),
+ configs=self.test_configs,
+ issues=self.testIssues,
+ unknown_issues=self.failedTestsWithUnknownIssues,
+ environment_compatible=self.testEnvironmentCompatible,
+ environment_misc=self.testEnvironmentMisc,
+ fail_reasons=self.testFailReasons,
+ failed_platforms=list(self.testPlatformsWithErrors),
+ ),
+ ),
+ filters=DetailsFilters(
+ all=GlobalFilters(
+ configs=list(self.global_configs),
+ architectures=list(self.global_architectures),
+ compilers=list(self.global_compilers),
+ ),
+ builds=LocalFilters(
+ issues=list(self.unfiltered_build_issues),
+ has_unknown_issue=self.unfiltered_uncategorized_issue_flags[
+ "build"
+ ],
+ ),
+ boots=LocalFilters(
+ issues=list(self.unfiltered_boot_issues),
+ has_unknown_issue=self.unfiltered_uncategorized_issue_flags[
+ "boot"
+ ],
+ ),
+ tests=LocalFilters(
+ issues=list(self.unfiltered_test_issues),
+ has_unknown_issue=self.unfiltered_uncategorized_issue_flags[
+ "test"
+ ],
+ ),
+ ),
+ )
except ValidationError as e:
- return Response(data=e.errors())
+ return Response(data=e.errors(), status=HTTPStatus.INTERNAL_SERVER_ERROR)
- return Response(response)
+ return Response(valid_response.model_dump(), status=HTTPStatus.OK)
diff --git a/backend/kernelCI_app/views/treeDetailsView.py b/backend/kernelCI_app/views/treeDetailsView.py
index 587c3b15d..9a8e951a2 100644
--- a/backend/kernelCI_app/views/treeDetailsView.py
+++ b/backend/kernelCI_app/views/treeDetailsView.py
@@ -1,7 +1,8 @@
-from typing import List
+from typing import Dict, List
from http import HTTPStatus
from rest_framework.views import APIView
from rest_framework.response import Response
+from kernelCI_app.helpers.commonDetails import PossibleTabs
from kernelCI_app.helpers.filters import (
FilterParams,
)
@@ -26,6 +27,14 @@
process_tests_issue,
process_filters,
)
+from kernelCI_app.typeModels.commonDetails import (
+ BuildSummary,
+ DetailsFilters,
+ GlobalFilters,
+ LocalFilters,
+ Summary,
+ TestSummary,
+)
from kernelCI_app.utils import (
convert_issues_dict_to_list,
)
@@ -36,8 +45,9 @@
from kernelCI_app.typeModels.issues import Issue, IssueDict
from kernelCI_app.typeModels.treeDetails import (
+ TreeCommon,
TreeDetailsFullResponse,
- TreeQueryParameters
+ TreeQueryParameters,
)
@@ -84,6 +94,11 @@ def __init__(self):
self.unfiltered_test_issues = set()
self.unfiltered_boot_issues = set()
self.unfiltered_build_issues = set()
+ self.unfiltered_uncategorized_issue_flags: Dict[PossibleTabs, bool] = {
+ "build": False,
+ "boot": False,
+ "test": False,
+ }
def _process_boots_test(self, row_data):
test_id = row_data["test_id"]
@@ -168,7 +183,7 @@ def _sanitize_rows(self, rows):
@extend_schema(
responses=TreeDetailsFullResponse,
parameters=[TreeQueryParameters],
- methods=["GET"]
+ methods=["GET"],
)
def get(self, request, commit_hash: str | None):
rows = get_tree_details_data(request, commit_hash)
@@ -187,58 +202,67 @@ def get(self, request, commit_hash: str | None):
builds=self.builds,
boots=self.bootHistory,
tests=self.testHistory,
- summary={
- "builds": {
- "status": self.build_summary["builds"],
- "architectures": self.build_summary["architectures"],
- "configs": self.build_summary["configs"],
- "issues": self.build_issues,
- "unknown_issues": self.failed_builds_with_unknown_issues,
- },
- "boots": {
- "status": self.bootStatusSummary,
- "architectures": list(self.bootArchSummary.values()),
- "configs": self.bootConfigs,
- "issues": self.bootIssues,
- "unknown_issues": self.failedBootsWithUnknownIssues,
- "environment_compatible": self.bootEnvironmentCompatible,
- "environment_misc": self.bootEnvironmentMisc,
- "fail_reasons": self.bootFailReasons,
- "failed_platforms": list(self.bootPlatformsFailing),
- },
- "tests": {
- "status": self.testStatusSummary,
- "architectures": list(self.test_arch_summary.values()),
- "configs": self.test_configs,
- "issues": self.testIssues,
- "unknown_issues": self.failedTestsWithUnknownIssues,
- "environment_compatible": self.testEnvironmentCompatible,
- "environment_misc": self.testEnvironmentMisc,
- "fail_reasons": self.testFailReasons,
- "failed_platforms": list(self.testPlatformsWithErrors),
- },
- },
- common={
- "hardware": list(self.hardwareUsed),
- "tree_url": self.tree_url,
- "git_commit_tags": self.git_commit_tags,
- },
- filters={
- "all": {
- "configs": list(self.global_configs),
- "architectures": list(self.global_architectures),
- "compilers": list(self.global_compilers),
- },
- "builds": {
- "issues": list(self.unfiltered_build_issues),
- },
- "boots": {
- "issues": list(self.unfiltered_boot_issues),
- },
- "tests": {
- "issues": list(self.unfiltered_test_issues),
- },
- }
+ summary=Summary(
+ builds=BuildSummary(
+ status=self.build_summary["builds"],
+ architectures=self.build_summary["architectures"],
+ configs=self.build_summary["configs"],
+ issues=self.build_issues,
+ unknown_issues=self.failed_builds_with_unknown_issues,
+ ),
+ boots=TestSummary(
+ status=self.bootStatusSummary,
+ architectures=list(self.bootArchSummary.values()),
+ configs=self.bootConfigs,
+ issues=self.bootIssues,
+ unknown_issues=self.failedBootsWithUnknownIssues,
+ environment_compatible=self.bootEnvironmentCompatible,
+ environment_misc=self.bootEnvironmentMisc,
+ fail_reasons=self.bootFailReasons,
+ failed_platforms=list(self.bootPlatformsFailing),
+ ),
+ tests=TestSummary(
+ status=self.testStatusSummary,
+ architectures=list(self.test_arch_summary.values()),
+ configs=self.test_configs,
+ issues=self.testIssues,
+ unknown_issues=self.failedTestsWithUnknownIssues,
+ environment_compatible=self.testEnvironmentCompatible,
+ environment_misc=self.testEnvironmentMisc,
+ fail_reasons=self.testFailReasons,
+ failed_platforms=list(self.testPlatformsWithErrors),
+ ),
+ ),
+ common=TreeCommon(
+ hardware=list(self.hardwareUsed),
+ tree_url=self.tree_url,
+ git_commit_tags=self.git_commit_tags,
+ ),
+ filters=DetailsFilters(
+ all=GlobalFilters(
+ configs=list(self.global_configs),
+ architectures=list(self.global_architectures),
+ compilers=list(self.global_compilers),
+ ),
+ builds=LocalFilters(
+ issues=list(self.unfiltered_build_issues),
+ has_unknown_issue=self.unfiltered_uncategorized_issue_flags[
+ "build"
+ ],
+ ),
+ boots=LocalFilters(
+ issues=list(self.unfiltered_boot_issues),
+ has_unknown_issue=self.unfiltered_uncategorized_issue_flags[
+ "boot"
+ ],
+ ),
+ tests=LocalFilters(
+ issues=list(self.unfiltered_test_issues),
+ has_unknown_issue=self.unfiltered_uncategorized_issue_flags[
+ "test"
+ ],
+ ),
+ ),
)
except ValidationError as e:
return create_api_error_response(
diff --git a/backend/schema.yml b/backend/schema.yml
index 21b5379d0..06a7f15be 100644
--- a/backend/schema.yml
+++ b/backend/schema.yml
@@ -2029,6 +2029,9 @@ components:
type: array
title: Issues
type: array
+ has_unknown_issue:
+ title: Has Unknown Issue
+ type: boolean
platforms:
items:
type: string
@@ -2036,6 +2039,7 @@ components:
type: array
required:
- issues
+ - has_unknown_issue
- platforms
title: HardwareTestLocalFilters
type: object
@@ -2306,8 +2310,12 @@ components:
type: array
title: Issues
type: array
+ has_unknown_issue:
+ title: Has Unknown Issue
+ type: boolean
required:
- issues
+ - has_unknown_issue
title: LocalFilters
type: object
LogData:
@@ -2765,6 +2773,8 @@ components:
$ref: '#/components/schemas/Checkout__GitCommitHash'
git_commit_name:
$ref: '#/components/schemas/Checkout__GitCommitName'
+ git_commit_tags:
+ $ref: '#/components/schemas/Checkout__GitCommitTags'
earliest_start_time:
format: date-time
title: Earliest Start Time
@@ -2778,6 +2788,7 @@ components:
required:
- git_commit_hash
- git_commit_name
+ - git_commit_tags
- earliest_start_time
- builds
- boots
diff --git a/dashboard/src/components/Cards/IssuesList.tsx b/dashboard/src/components/Cards/IssuesList.tsx
index 24fb80e59..7c28fedc7 100644
--- a/dashboard/src/components/Cards/IssuesList.tsx
+++ b/dashboard/src/components/Cards/IssuesList.tsx
@@ -21,7 +21,7 @@ import FilterLink from '@/components/Tabs/FilterLink';
import LinkWithIcon from '@/components/LinkWithIcon/LinkWithIcon';
-import { UNKNOWN_STRING } from '@/utils/constants/backend';
+import { UNCATEGORIZED_STRING } from '@/utils/constants/backend';
import { MemoizedMoreDetailsIconLink } from '@/components/Button/MoreDetailsButton';
import { IssueTooltip } from '@/components/Issue/IssueTooltip';
@@ -144,7 +144,8 @@ const IssuesList = ({
unknown={issue.incidents_info.incidentsCount}
hasBottomBorder
text={
- issue.comment ?? formatMessage({ id: 'global.unknown' })
+ issue.comment ??
+ formatMessage({ id: 'issue.uncategorized' })
}
tooltip={issue.comment}
/>
@@ -176,7 +177,7 @@ const IssuesList = ({
icon={}
/>
)}
- {issue.id !== UNKNOWN_STRING && (
+ {issue.id !== UNCATEGORIZED_STRING && (
@@ -188,12 +189,12 @@ const IssuesList = ({
{failedWithUnknownIssues && (
)}
diff --git a/dashboard/src/components/Tabs/Filters.tsx b/dashboard/src/components/Tabs/Filters.tsx
index 04575f690..1300f144f 100644
--- a/dashboard/src/components/Tabs/Filters.tsx
+++ b/dashboard/src/components/Tabs/Filters.tsx
@@ -19,7 +19,7 @@ import type {
TFilterNumberKeys,
} from '@/types/general';
import { filterFieldMap, zFilterObjectsKeys } from '@/types/general';
-import { UNKNOWN_STRING } from '@/utils/constants/backend';
+import { UNCATEGORIZED_STRING } from '@/utils/constants/backend';
import { version_prefix } from '@/utils/utils';
export const NO_VALID_INDEX = -1;
@@ -55,10 +55,10 @@ export const mapFilterToReq = (filter: TFilter): TFilter => {
}
if (reqField.includes('issue')) {
- let issue_id = UNKNOWN_STRING;
+ let issue_id = UNCATEGORIZED_STRING;
let issue_version = null;
- if (value !== UNKNOWN_STRING) {
+ if (value !== UNCATEGORIZED_STRING) {
const split_issue_data = value.split(` ${version_prefix}`);
issue_version = split_issue_data.pop();
issue_id = split_issue_data.join(` ${version_prefix}`);
diff --git a/dashboard/src/locales/messages/index.ts b/dashboard/src/locales/messages/index.ts
index d7541fa62..3675296f3 100644
--- a/dashboard/src/locales/messages/index.ts
+++ b/dashboard/src/locales/messages/index.ts
@@ -199,6 +199,7 @@ export const messages = {
'issue.noIssueFound': 'No issue found.',
'issue.tooltip':
'Issues groups several builds or tests by matching result status and logs.{br}They may also be linked to an external issue tracker or mailing list discussion.',
+ 'issue.uncategorized': 'Uncategorized',
'issueDetails.buildValid': 'Build Valid',
'issueDetails.comment': 'Comment',
'issueDetails.culpritCode': 'Code',
diff --git a/dashboard/src/pages/TreeDetails/TreeDetailsFilter.tsx b/dashboard/src/pages/TreeDetails/TreeDetailsFilter.tsx
index bb37d86b9..9a3b06799 100644
--- a/dashboard/src/pages/TreeDetails/TreeDetailsFilter.tsx
+++ b/dashboard/src/pages/TreeDetails/TreeDetailsFilter.tsx
@@ -16,6 +16,7 @@ import {
import { isTFilterObjectKeys, type TFilter } from '@/types/general';
import { cleanFalseFilters } from '@/components/Tabs/tabsUtils';
import { getIssueFilterLabel } from '@/utils/utils';
+import { UNCATEGORIZED_STRING } from '@/utils/constants/backend';
type TFilterValues = Record;
@@ -54,18 +55,32 @@ export const createFilter = (data: TreeDetailsSummary | undefined): TFilter => {
data.common.hardware.forEach(h => (hardware[h] = false));
- data.filters.builds.issues.forEach(
+ const buildFilters = data.filters.builds;
+ buildFilters.issues.forEach(
i =>
(buildIssue[getIssueFilterLabel({ id: i[0], version: i[1] })] = false),
);
- data.filters.boots.issues.forEach(
+ if (buildFilters.has_unknown_issue) {
+ buildIssue[UNCATEGORIZED_STRING] = false;
+ }
+
+ const bootFilters = data.filters.boots;
+ bootFilters.issues.forEach(
i =>
(bootIssue[getIssueFilterLabel({ id: i[0], version: i[1] })] = false),
);
- data.filters.tests.issues.forEach(
+ if (bootFilters.has_unknown_issue) {
+ bootIssue[UNCATEGORIZED_STRING] = false;
+ }
+
+ const testFilters = data.filters.tests;
+ testFilters.issues.forEach(
i =>
(testIssue[getIssueFilterLabel({ id: i[0], version: i[1] })] = false),
);
+ if (testFilters.has_unknown_issue) {
+ testIssue[UNCATEGORIZED_STRING] = false;
+ }
}
return {
diff --git a/dashboard/src/pages/hardwareDetails/HardwareDetailsFilter.tsx b/dashboard/src/pages/hardwareDetails/HardwareDetailsFilter.tsx
index d931c5511..60d67fe67 100644
--- a/dashboard/src/pages/hardwareDetails/HardwareDetailsFilter.tsx
+++ b/dashboard/src/pages/hardwareDetails/HardwareDetailsFilter.tsx
@@ -19,6 +19,7 @@ import { isTFilterObjectKeys, type TFilter } from '@/types/general';
import { cleanFalseFilters } from '@/components/Tabs/tabsUtils';
import type { HardwareDetailsSummary } from '@/types/hardware/hardwareDetails';
import { getIssueFilterLabel } from '@/utils/utils';
+import { UNCATEGORIZED_STRING } from '@/utils/constants/backend';
type TFilterValues = Record;
@@ -83,18 +84,34 @@ export const createFilter = (
data.filters.all.configs.forEach(config => {
configs[config ?? 'Unknown'] = false;
});
- data.filters.builds.issues.forEach(
+
+ const buildFilters = data.filters.builds;
+ buildFilters.issues.forEach(
i =>
(buildIssue[getIssueFilterLabel({ id: i[0], version: i[1] })] = false),
);
- data.filters.boots.issues.forEach(
+ if (buildFilters.has_unknown_issue) {
+ buildIssue[UNCATEGORIZED_STRING] = false;
+ }
+
+ const bootFilters = data.filters.boots;
+ bootFilters.issues.forEach(
i =>
(bootIssue[getIssueFilterLabel({ id: i[0], version: i[1] })] = false),
);
- data.filters.tests.issues.forEach(
+ if (bootFilters.has_unknown_issue) {
+ bootIssue[UNCATEGORIZED_STRING] = false;
+ }
+
+ const testFilters = data.filters.tests;
+ testFilters.issues.forEach(
i =>
(testIssue[getIssueFilterLabel({ id: i[0], version: i[1] })] = false),
);
+ if (testFilters.has_unknown_issue) {
+ testIssue[UNCATEGORIZED_STRING] = false;
+ }
+
(data.filters.boots.platforms ?? []).forEach(
p => (bootPlatform[p] = false),
);
diff --git a/dashboard/src/types/commonDetails.ts b/dashboard/src/types/commonDetails.ts
index e39da39d2..aadceefe1 100644
--- a/dashboard/src/types/commonDetails.ts
+++ b/dashboard/src/types/commonDetails.ts
@@ -44,6 +44,7 @@ export type IssueFilterItem = [string, number?];
export type LocalFilters = {
issues: IssueFilterItem[];
+ has_unknown_issue: boolean;
};
export type DetailsFilters = {
diff --git a/dashboard/src/utils/constants/backend.ts b/dashboard/src/utils/constants/backend.ts
index 58608bef2..0fc22405a 100644
--- a/dashboard/src/utils/constants/backend.ts
+++ b/dashboard/src/utils/constants/backend.ts
@@ -1 +1,2 @@
export const UNKNOWN_STRING = 'Unknown';
+export const UNCATEGORIZED_STRING = 'Uncategorized';