fix: Render sub-labels for Frigate reviews (#2468)

- Closes: #2438
This commit is contained in:
Dermot Duffy
2026-06-30 17:45:12 -07:00
committed by dermotduffy
parent 149fce4823
commit 591f466b92
5 changed files with 61 additions and 4 deletions
+1
View File
@@ -115,6 +115,7 @@ export interface NativeFrigateEventQuery {
instance_id?: string;
cameras?: string[];
labels?: string[];
sub_labels?: string[];
zones?: string[];
after?: number;
before?: number;
+5
View File
@@ -155,6 +155,11 @@ export type FrigateReviewSeverity =
// Review data schema (only fields we need for display)
const frigateReviewDataSchema = z.object({
objects: z.string().array().optional(),
// Frigate adds sub_labels for non-attribute identifications (e.g. Frigate+
// species name for a bird). Note: The matching `objects` entry carries a
// `-verified` suffix in that case.
sub_labels: z.string().array().optional(),
zones: z.string().array().optional(),
metadata: z
.object({
+16 -4
View File
@@ -105,12 +105,24 @@ export const getRecordingID = (
* @param review The Frigate review item.
*/
export const getReviewTitle = (review: FrigateReview): string => {
// Frigate flags Frigate+ verified detections by suffixing the object label
// with `-verified`. The user-meaningful identification (e.g. species) lives
// in `sub_labels` instead, so strip the suffix and surface sub_labels as a
// tag separated by ': ' (matching event title formatting).
const objects = review.data.objects?.length
? review.data.objects.map((o) => prettifyTitle(o)).join(', ')
? review.data.objects
.map((o) => prettifyTitle(o.replace(/-verified$/, '')))
.join(', ')
: '';
const subLabels = review.data.sub_labels?.length
? review.data.sub_labels.map((s) => prettifyTitle(s)).join(', ')
: '';
if (objects) {
return objects;
if (objects && subLabels) {
return `${objects}: ${subLabels}`;
}
if (objects || subLabels) {
return objects || subLabels;
}
const durationSeconds = Math.round(
@@ -121,7 +133,7 @@ export const getReviewTitle = (review: FrigateReview): string => {
return `${formatDateAndTime(
new Date(review.start_time * 1000),
)} [${durationSeconds}s${objects}]`;
)} [${durationSeconds}s]`;
};
/**
@@ -146,6 +146,7 @@ describe('frigate requests', () => {
instance_id: 'clientID',
cameras: ['camera.office'],
labels: ['person'],
sub_labels: ['John'],
zones: ['zone'],
after: 0,
before: 1,
@@ -163,6 +164,7 @@ describe('frigate requests', () => {
instance_id: 'clientID',
cameras: ['camera.office'],
labels: ['person'],
sub_labels: ['John'],
zones: ['zone'],
after: 0,
before: 1,
+37
View File
@@ -195,6 +195,43 @@ describe('getReviewTitle', () => {
).toBe('Person, Dog');
});
it('should append sub_labels as tags after objects', () => {
expect(
getReviewTitle(
createFrigateReview({
data: {
objects: ['bird-verified'],
sub_labels: ['Mourning Dove'],
},
}),
),
).toBe('Bird: Mourning Dove');
});
it('should strip -verified suffix from objects', () => {
expect(
getReviewTitle(
createFrigateReview({
data: {
objects: ['bird-verified', 'person'],
},
}),
),
).toBe('Bird, Person');
});
it('should fall back to sub_labels when objects are absent', () => {
expect(
getReviewTitle(
createFrigateReview({
data: {
sub_labels: ['John'],
},
}),
),
).toBe('John');
});
it('should get in-progress review title', () => {
vi.useFakeTimers();
vi.setSystemTime(add(start, { seconds: 60 }));