Improve display of recordings data (#16436)

* backend

* frontend

* add earliest recording available to storage metrics page
This commit is contained in:
Josh Hawkins
2025-02-09 16:02:36 -07:00
committed by GitHub
parent bc96db8612
commit d5b60237a2
11 changed files with 217 additions and 39 deletions
@@ -7,7 +7,12 @@ import { ExportContent, ExportPreviewDialog } from "./ExportDialog";
import { ExportMode, GeneralFilter } from "@/types/filter";
import ReviewActivityCalendar from "./ReviewActivityCalendar";
import { SelectSeparator } from "../ui/select";
import { ReviewFilter, ReviewSeverity, ReviewSummary } from "@/types/review";
import {
RecordingsSummary,
ReviewFilter,
ReviewSeverity,
ReviewSummary,
} from "@/types/review";
import { getEndOfDayTimestamp } from "@/utils/dateUtil";
import { GeneralFilterContent } from "../filter/ReviewFilterGroup";
import { toast } from "sonner";
@@ -36,6 +41,7 @@ type MobileReviewSettingsDrawerProps = {
mode: ExportMode;
showExportPreview: boolean;
reviewSummary?: ReviewSummary;
recordingsSummary?: RecordingsSummary;
allLabels: string[];
allZones: string[];
onUpdateFilter: (filter: ReviewFilter) => void;
@@ -54,6 +60,7 @@ export default function MobileReviewSettingsDrawer({
mode,
showExportPreview,
reviewSummary,
recordingsSummary,
allLabels,
allZones,
onUpdateFilter,
@@ -211,6 +218,7 @@ export default function MobileReviewSettingsDrawer({
<div className="flex w-full flex-row justify-center">
<ReviewActivityCalendar
reviewSummary={reviewSummary}
recordingsSummary={recordingsSummary}
selectedDay={
filter?.after == undefined
? undefined
@@ -1,4 +1,4 @@
import { ReviewSummary } from "@/types/review";
import { RecordingsSummary, ReviewSummary } from "@/types/review";
import { Calendar } from "../ui/calendar";
import { useMemo } from "react";
import { FaCircle } from "react-icons/fa";
@@ -6,16 +6,19 @@ import { getUTCOffset } from "@/utils/dateUtil";
import { type DayContentProps } from "react-day-picker";
import { LAST_24_HOURS_KEY } from "@/types/filter";
import { usePersistence } from "@/hooks/use-persistence";
import { cn } from "@/lib/utils";
type WeekStartsOnType = 0 | 1 | 2 | 3 | 4 | 5 | 6;
type ReviewActivityCalendarProps = {
reviewSummary?: ReviewSummary;
recordingsSummary?: RecordingsSummary;
selectedDay?: Date;
onSelect: (day?: Date) => void;
};
export default function ReviewActivityCalendar({
reviewSummary,
recordingsSummary,
selectedDay,
onSelect,
}: ReviewActivityCalendarProps) {
@@ -30,39 +33,56 @@ export default function ReviewActivityCalendar({
}, []);
const modifiers = useMemo(() => {
if (!reviewSummary) {
return { alerts: [], detections: [] };
const recordings: Date[] = [];
const alerts: Date[] = [];
const detections: Date[] = [];
// Handle recordings
if (recordingsSummary) {
Object.keys(recordingsSummary).forEach((date) => {
if (date === LAST_24_HOURS_KEY) {
return;
}
const parts = date.split("-");
const cal = new Date(date);
cal.setFullYear(
parseInt(parts[0]),
parseInt(parts[1]) - 1,
parseInt(parts[2]),
);
recordings.push(cal);
});
}
const unreviewedDetections: Date[] = [];
const unreviewedAlerts: Date[] = [];
// Handle reviews if present
if (reviewSummary) {
Object.entries(reviewSummary).forEach(([date, data]) => {
if (date === LAST_24_HOURS_KEY) {
return;
}
Object.entries(reviewSummary).forEach(([date, data]) => {
if (date == LAST_24_HOURS_KEY) {
return;
}
const parts = date.split("-");
const cal = new Date(date);
const parts = date.split("-");
const cal = new Date(date);
cal.setFullYear(
parseInt(parts[0]),
parseInt(parts[1]) - 1,
parseInt(parts[2]),
);
cal.setFullYear(
parseInt(parts[0]),
parseInt(parts[1]) - 1,
parseInt(parts[2]),
);
if (data.total_alert > data.reviewed_alert) {
alerts.push(cal);
} else if (data.total_detection > data.reviewed_detection) {
detections.push(cal);
}
});
}
if (data.total_alert > data.reviewed_alert) {
unreviewedAlerts.push(cal);
} else if (data.total_detection > data.reviewed_detection) {
unreviewedDetections.push(cal);
}
});
return {
alerts: unreviewedAlerts,
detections: unreviewedDetections,
};
}, [reviewSummary]);
return { alerts, detections, recordings };
}, [reviewSummary, recordingsSummary]);
return (
<Calendar
@@ -94,13 +114,30 @@ function ReviewActivityDay({ date, activeModifiers }: DayContentProps) {
}, [activeModifiers]);
return (
<div className="flex flex-col items-center justify-center gap-0.5">
{date.getDate()}
{dayActivity != "none" && (
<FaCircle
className={`size-2 ${dayActivity == "alert" ? "fill-severity_alert" : "fill-severity_detection"}`}
/>
)}
<div className={cn("flex flex-col items-center justify-center gap-0.5")}>
<span
className={cn(
"w-4",
activeModifiers["recordings"]
? "border-b border-primary/60 text-primary"
: "text-primary/40",
activeModifiers.selected && "border-white text-white",
)}
>
{date.getDate()}
</span>
<div className="mt-0.5 flex h-2 flex-row gap-0.5">
{dayActivity != "none" && (
<FaCircle
size={6}
className={cn(
dayActivity == "alert"
? "fill-severity_alert"
: "fill-severity_detection",
)}
/>
)}
</div>
</div>
);
}