Implement label and review filters

This commit is contained in:
Nicolas Mowen 2024-02-25 07:26:21 -07:00
parent 01ded3fcc5
commit 335f88f5e6
3 changed files with 169 additions and 9 deletions

View File

@ -2396,6 +2396,8 @@ def vod_event(id):
@bp.route("/review") @bp.route("/review")
def review(): def review():
cameras = request.args.get("cameras", "all") cameras = request.args.get("cameras", "all")
labels = request.args.get("labels", "all")
reviewed = request.args.get("reviewed", default=False)
limit = request.args.get("limit", 100) limit = request.args.get("limit", 100)
severity = request.args.get("severity", None) severity = request.args.get("severity", None)
@ -2410,6 +2412,23 @@ def review():
camera_list = cameras.split(",") camera_list = cameras.split(",")
clauses.append((ReviewSegment.camera << camera_list)) clauses.append((ReviewSegment.camera << camera_list))
if labels != "all":
# use matching so segments with multiple labels
# still match on a search where any label matches
label_clauses = []
filtered_labels = labels.split(",")
for label in filtered_labels:
label_clauses.append(
(ReviewSegment.data["objects"].cast("text") % f'*"{label}"*')
)
label_clause = reduce(operator.or_, label_clauses)
clauses.append((label_clause))
if not reviewed:
clauses.append((ReviewSegment.has_been_reviewed == False))
if severity: if severity:
clauses.append((ReviewSegment.severity == severity)) clauses.append((ReviewSegment.severity == severity))

View File

@ -8,8 +8,6 @@ import {
DropdownMenu, DropdownMenu,
DropdownMenuContent, DropdownMenuContent,
DropdownMenuLabel, DropdownMenuLabel,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuSeparator, DropdownMenuSeparator,
DropdownMenuTrigger, DropdownMenuTrigger,
} from "../ui/dropdown-menu"; } from "../ui/dropdown-menu";
@ -17,6 +15,8 @@ import { Calendar } from "../ui/calendar";
import { ReviewFilter } from "@/types/review"; import { ReviewFilter } from "@/types/review";
import { formatUnixTimestampToDateTime } from "@/utils/dateUtil"; import { formatUnixTimestampToDateTime } from "@/utils/dateUtil";
const ATTRIBUTES = ["amazon", "face", "fedex", "license_plate", "ups"];
type ReviewFilterGroupProps = { type ReviewFilterGroupProps = {
filter?: ReviewFilter; filter?: ReviewFilter;
onUpdateFilter: (filter: ReviewFilter) => void; onUpdateFilter: (filter: ReviewFilter) => void;
@ -28,9 +28,25 @@ export default function ReviewFilterGroup({
}: ReviewFilterGroupProps) { }: ReviewFilterGroupProps) {
const { data: config } = useSWR<FrigateConfig>("config"); const { data: config } = useSWR<FrigateConfig>("config");
const { data: allLabels } = useSWR<string[]>(["labels"], { const allLabels = useMemo<string[]>(() => {
revalidateOnFocus: false, if (!config) {
}); return [];
}
const labels = new Set<string>();
const cameras = filter?.cameras || Object.keys(config.cameras);
cameras.forEach((camera) => {
config.cameras[camera].objects.track.forEach((label) => {
if (!ATTRIBUTES.includes(label)) {
labels.add(label);
}
});
});
return [...labels];
}, [config, filter]);
const filterValues = useMemo( const filterValues = useMemo(
() => ({ () => ({
cameras: Object.keys(config?.cameras || {}), cameras: Object.keys(config?.cameras || {}),
@ -49,10 +65,17 @@ export default function ReviewFilterGroup({
}} }}
/> />
<CalendarFilterButton before={filter?.before} after={filter?.after} /> <CalendarFilterButton before={filter?.before} after={filter?.after} />
<Button className="mx-1" variant="secondary"> <GeneralFilterButton
<LuFilter className=" mr-[10px]" /> allLabels={filterValues.labels}
Filter selectedLabels={filter?.labels}
</Button> updateLabelFilter={(newLabels) => {
onUpdateFilter({ ...filter, labels: newLabels });
}}
showReviewed={filter?.showReviewed || false}
setShowReviewed={(reviewed) =>
onUpdateFilter({ ...filter, showReviewed: reviewed })
}
/>
</div> </div>
); );
@ -285,6 +308,7 @@ function CalendarFilterButton({ before, after }: CalendarFilterButtonProps) {
future.setFullYear(tomorrow.getFullYear() + 10); future.setFullYear(tomorrow.getFullYear() + 10);
return { from: tomorrow, to: future }; return { from: tomorrow, to: future };
}, []); }, []);
// @ts-ignore
const dateRange = useMemo(() => { const dateRange = useMemo(() => {
return before == undefined || after == undefined return before == undefined || after == undefined
? undefined ? undefined
@ -311,6 +335,118 @@ function CalendarFilterButton({ before, after }: CalendarFilterButtonProps) {
); );
} }
type GeneralFilterButtonProps = {
allLabels: string[];
selectedLabels: string[] | undefined;
updateLabelFilter: (labels: string[] | undefined) => void;
showReviewed: boolean;
setShowReviewed: (reviewed: boolean) => void;
};
function GeneralFilterButton({
allLabels,
selectedLabels,
updateLabelFilter,
showReviewed,
setShowReviewed,
}: GeneralFilterButtonProps) {
return (
<Popover>
<PopoverTrigger asChild>
<Button className="mx-1" variant="secondary">
<LuFilter className=" mr-[10px]" />
Filter
</Button>
</PopoverTrigger>
<PopoverContent side="left" asChild>
<div className="w-80 flex">
<LabelsFilterButton
allLabels={allLabels}
selectedLabels={selectedLabels}
updateLabelFilter={updateLabelFilter}
/>
<FilterCheckBox
label="Show Reviewed"
isChecked={showReviewed}
onCheckedChange={(isChecked) => setShowReviewed(isChecked)}
/>
</div>
</PopoverContent>
</Popover>
);
}
type LabelFilterButtonProps = {
allLabels: string[];
selectedLabels: string[] | undefined;
updateLabelFilter: (labels: string[] | undefined) => void;
};
function LabelsFilterButton({
allLabels,
selectedLabels,
updateLabelFilter,
}: LabelFilterButtonProps) {
const [currentLabels, setCurrentLabels] = useState<string[] | undefined>(
selectedLabels
);
return (
<DropdownMenu
onOpenChange={(open) => {
if (!open) {
updateLabelFilter(currentLabels);
}
}}
>
<DropdownMenuTrigger asChild>
<Button className="mx-1 capitalize" variant="secondary">
<LuVideo className=" mr-[10px]" />
{selectedLabels == undefined
? "All Labels"
: `${selectedLabels.length} Labels`}
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuLabel>Filter Labels</DropdownMenuLabel>
<DropdownMenuSeparator />
<FilterCheckBox
isChecked={currentLabels == undefined}
label="All Labels"
onCheckedChange={(isChecked) => {
if (isChecked) {
setCurrentLabels(undefined);
}
}}
/>
<DropdownMenuSeparator />
{allLabels.map((item) => (
<FilterCheckBox
key={item}
isChecked={currentLabels?.includes(item) ?? false}
label={item.replaceAll("_", " ")}
onCheckedChange={(isChecked) => {
if (isChecked) {
const updatedLabels = currentLabels ? [...currentLabels] : [];
updatedLabels.push(item);
setCurrentLabels(updatedLabels);
} else {
const updatedLabels = currentLabels ? [...currentLabels] : [];
// can not deselect the last item
if (updatedLabels.length > 1) {
updatedLabels.splice(updatedLabels.indexOf(item), 1);
setCurrentLabels(updatedLabels);
}
}
}}
/>
))}
<DropdownMenuSeparator />
</DropdownMenuContent>
</DropdownMenu>
);
}
type FilterCheckBoxProps = { type FilterCheckBoxProps = {
label: string; label: string;
isChecked: boolean; isChecked: boolean;

View File

@ -34,11 +34,14 @@ export default function Events() {
const getKey = useCallback( const getKey = useCallback(
(index: number, prevData: ReviewSegment[]) => { (index: number, prevData: ReviewSegment[]) => {
console.log("The params are " + JSON.stringify(reviewSearchParams))
if (index > 0) { if (index > 0) {
const lastDate = prevData[prevData.length - 1].start_time; const lastDate = prevData[prevData.length - 1].start_time;
reviewSearchParams; reviewSearchParams;
const pagedParams = { const pagedParams = {
cameras: reviewSearchParams["cameras"], cameras: reviewSearchParams["cameras"],
labels: reviewSearchParams["labels"],
reviewed: reviewSearchParams["showReviewed"],
before: lastDate, before: lastDate,
after: reviewSearchParams["after"] || timeRange.after, after: reviewSearchParams["after"] || timeRange.after,
limit: API_LIMIT, limit: API_LIMIT,
@ -48,6 +51,8 @@ export default function Events() {
const params = { const params = {
cameras: reviewSearchParams["cameras"], cameras: reviewSearchParams["cameras"],
labels: reviewSearchParams["labels"],
reviewed: reviewSearchParams["showReviewed"],
limit: API_LIMIT, limit: API_LIMIT,
before: reviewSearchParams["before"] || timeRange.before, before: reviewSearchParams["before"] || timeRange.before,
after: reviewSearchParams["after"] || timeRange.after, after: reviewSearchParams["after"] || timeRange.after,