Miscellaneous fixes (#24820)

* keep base path in logout redirect

* store manual event sub labels as verified objects

Manual events created with a sub label were stored in the review item as "label: sub_label", so review previews and collapsed History cards showed a generic icon, the review label filter missed them, and the sub label never reached sub_labels for chips, notifications, or GenAI prompts. The object processor now publishes the label and sub label separately, and the review maintainer stores them as label-verified plus a sub_labels entry, the same as tracked objects.

* only expire snapshots for events that still have one

Snapshot expiry selected every event older than snapshot retention, including events whose snapshot was already deleted, so with snapshot retention shorter than alert/detection retention the same missing files were unlinked again every cleanup cycle until the event row expired. That was nearly free on local disk but caused constant metadata load on network shares. The expiry queries now filter on has_snapshot so each snapshot is deleted once.
This commit is contained in:
Josh Hawkins
2026-10-29 09:04:16 -05:00
committed by GitHub
parent ff2cd6a417
commit 4953c19a71
7 changed files with 165 additions and 38 deletions
+55 -7
View File
@@ -19,6 +19,7 @@ import numpy as np
from frigate.comms.detections_updater import DetectionTypeEnum
from frigate.config import FrigateConfig
from frigate.review.maintainer import ReviewSegmentMaintainer
from frigate.track.object_processing import ManualEventState
CAMERA = "front_door"
@@ -122,13 +123,19 @@ class ReviewFlowTestCase(unittest.TestCase):
def feed(self, *frames: tuple[float, list[dict[str, Any]]]) -> None:
"""Run the maintainer loop over the given (frame_time, objects) frames."""
queue = [
(
DetectionTypeEnum.video.value,
(CAMERA, f"{CAMERA}_{frame_time}", frame_time, objects, [], []),
)
for frame_time, objects in frames
]
self.feed_messages(
*[
(
DetectionTypeEnum.video.value,
(CAMERA, f"{CAMERA}_{frame_time}", frame_time, objects, [], []),
)
for frame_time, objects in frames
]
)
def feed_messages(self, *messages: tuple[str, tuple[Any, ...]]) -> None:
"""Run the maintainer loop over the given (topic, data) messages."""
queue = list(messages)
self.maintainer.stop_event = threading.Event()
def next_update(timeout: float) -> Any:
@@ -853,5 +860,46 @@ class TestSplitDetectionLabels(ReviewFlowTestCase):
self.assertCountEqual(detection["data"]["zones"], ["yard", "driveway"])
class TestManualEvents(ReviewFlowTestCase):
def manual_event(
self, event_id: str, frame_time: float, sub_label: str | None = None
) -> tuple[str, tuple[Any, ...]]:
"""Build a completed manual event as published by the object processor."""
return (
DetectionTypeEnum.api.value,
(
CAMERA,
frame_time,
{
"state": ManualEventState.complete,
"label": "person",
"sub_label": sub_label,
"event_id": event_id,
"end_time": frame_time + 5,
},
),
)
def test_sub_label_is_verified_on_new_segment(self) -> None:
self.feed_messages(self.manual_event("m1", 1, "Bob"))
data = self.maintainer.active_review_segments[CAMERA].get_data(False)["data"]
self.assertEqual(data["objects"], ["person-verified"])
self.assertEqual(data["verified_objects"], ["person-verified"])
self.assertEqual(data["sub_labels"], ["Bob"])
def test_sub_label_is_verified_on_active_segment(self) -> None:
self.feed(
(1, [self.tracked("dog1", "dog", 1)]),
)
self.feed_messages(
self.manual_event("m1", 2, "Bob"), self.manual_event("m2", 2)
)
data = self.maintainer.active_review_segments[CAMERA].get_data(False)["data"]
self.assertCountEqual(data["objects"], ["dog", "person-verified", "person"])
self.assertEqual(data["sub_labels"], ["Bob"])
if __name__ == "__main__":
unittest.main()