mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-05-09 06:55:28 +03:00
Compare commits
No commits in common. "e593ac9599bdc440b180e11e9abbf230db7df07d" and "13e4a9406531ada6b4a638dac1d28c1f04e07c10" have entirely different histories.
e593ac9599
...
13e4a94065
@ -596,7 +596,7 @@ def export_recordings_batch(
|
||||
)
|
||||
try:
|
||||
start_export_job(request.app.frigate_config, export_job)
|
||||
except Exception:
|
||||
except Exception as err:
|
||||
logger.exception("Failed to queue export job %s", export_job.id)
|
||||
results.append(
|
||||
{
|
||||
@ -604,7 +604,7 @@ def export_recordings_batch(
|
||||
"export_id": None,
|
||||
"success": False,
|
||||
"status": None,
|
||||
"error": "Failed to queue export job",
|
||||
"error": str(err),
|
||||
"item_index": index,
|
||||
"client_item_id": item.client_item_id,
|
||||
}
|
||||
|
||||
@ -460,9 +460,10 @@ test.describe("Multi-Review Export @high", () => {
|
||||
.filter({ hasText: /Export 2 reviews/i });
|
||||
await expect(dialog).toBeVisible({ timeout: 5_000 });
|
||||
// The dialog uses a Select trigger for case selection (admins). The
|
||||
// default "None" value is shown on the trigger.
|
||||
// default "Create new case" value is shown on the trigger and the
|
||||
// New-case inputs render directly below.
|
||||
await expect(dialog.locator("button[role='combobox']")).toBeVisible();
|
||||
await expect(dialog.getByText(/None/)).toBeVisible();
|
||||
await expect(dialog.getByText(/Create new case/i)).toBeVisible();
|
||||
});
|
||||
|
||||
test("starting an export posts the expected payload and navigates to the case", async ({
|
||||
@ -512,12 +513,6 @@ test.describe("Multi-Review Export @high", () => {
|
||||
.filter({ hasText: /Export 2 reviews/i });
|
||||
await expect(dialog).toBeVisible({ timeout: 5_000 });
|
||||
|
||||
// Select "Create new case" from the case dropdown (default is "None")
|
||||
await dialog.locator("button[role='combobox']").click();
|
||||
await frigateApp.page
|
||||
.getByRole("option", { name: /Create new case/i })
|
||||
.click();
|
||||
|
||||
const nameInput = dialog.locator("input").first();
|
||||
await nameInput.fill("E2E Incident");
|
||||
|
||||
|
||||
@ -81,13 +81,11 @@
|
||||
"exportButton_other": "Export {{count}} Cameras"
|
||||
},
|
||||
"multi": {
|
||||
"title": "Export {{count}} reviews",
|
||||
"title_one": "Export 1 review",
|
||||
"title_other": "Export {{count}} reviews",
|
||||
"description": "Export each selected review. All exports will be grouped under a single case.",
|
||||
"descriptionNoCase": "Export each selected review.",
|
||||
"caseNamePlaceholder": "Review export - {{date}}",
|
||||
"exportButton": "Export {{count}} reviews",
|
||||
"exportButton_one": "Export 1 review",
|
||||
"exportButton_other": "Export {{count}} reviews",
|
||||
"exportingButton": "Exporting...",
|
||||
|
||||
@ -93,8 +93,6 @@ export default function ExportDialog({
|
||||
const { t } = useTranslation(["components/dialog"]);
|
||||
const [name, setName] = useState("");
|
||||
const [selectedCaseId, setSelectedCaseId] = useState<string | undefined>();
|
||||
const [singleNewCaseName, setSingleNewCaseName] = useState("");
|
||||
const [singleNewCaseDescription, setSingleNewCaseDescription] = useState("");
|
||||
const [activeTab, setActiveTab] = useState<ExportTab>("export");
|
||||
const [isStartingExport, setIsStartingExport] = useState(false);
|
||||
const previousModeRef = useRef<ExportMode>(mode);
|
||||
@ -139,24 +137,12 @@ export default function ExportDialog({
|
||||
setIsStartingExport(true);
|
||||
|
||||
try {
|
||||
let exportCaseId: string | undefined = selectedCaseId;
|
||||
|
||||
if (selectedCaseId === "new" && singleNewCaseName.trim().length > 0) {
|
||||
const caseResp = await axios.post("cases", {
|
||||
name: singleNewCaseName.trim(),
|
||||
description: singleNewCaseDescription.trim() || undefined,
|
||||
});
|
||||
exportCaseId = caseResp.data?.id;
|
||||
} else if (selectedCaseId === "new" || selectedCaseId === "none") {
|
||||
exportCaseId = undefined;
|
||||
}
|
||||
|
||||
await axios.post<StartExportResponse>(
|
||||
`export/${camera}/start/${Math.round(range.after)}/end/${Math.round(range.before)}`,
|
||||
{
|
||||
source: "recordings",
|
||||
name,
|
||||
export_case_id: exportCaseId,
|
||||
export_case_id: selectedCaseId || undefined,
|
||||
},
|
||||
);
|
||||
|
||||
@ -170,8 +156,6 @@ export default function ExportDialog({
|
||||
});
|
||||
setName("");
|
||||
setSelectedCaseId(undefined);
|
||||
setSingleNewCaseName("");
|
||||
setSingleNewCaseDescription("");
|
||||
setRange(undefined);
|
||||
setMode("none");
|
||||
return true;
|
||||
@ -199,8 +183,6 @@ export default function ExportDialog({
|
||||
name,
|
||||
range,
|
||||
selectedCaseId,
|
||||
singleNewCaseDescription,
|
||||
singleNewCaseName,
|
||||
setMode,
|
||||
setRange,
|
||||
t,
|
||||
@ -209,8 +191,6 @@ export default function ExportDialog({
|
||||
const handleCancel = useCallback(() => {
|
||||
setName("");
|
||||
setSelectedCaseId(undefined);
|
||||
setSingleNewCaseName("");
|
||||
setSingleNewCaseDescription("");
|
||||
setMode("none");
|
||||
setRange(undefined);
|
||||
setActiveTab("export");
|
||||
@ -292,16 +272,12 @@ export default function ExportDialog({
|
||||
range={range}
|
||||
name={name}
|
||||
selectedCaseId={selectedCaseId}
|
||||
singleNewCaseName={singleNewCaseName}
|
||||
singleNewCaseDescription={singleNewCaseDescription}
|
||||
activeTab={activeTab}
|
||||
isStartingExport={isStartingExport}
|
||||
onStartExport={onStartExport}
|
||||
setActiveTab={setActiveTab}
|
||||
setName={setName}
|
||||
setSelectedCaseId={setSelectedCaseId}
|
||||
setSingleNewCaseName={setSingleNewCaseName}
|
||||
setSingleNewCaseDescription={setSingleNewCaseDescription}
|
||||
setRange={setRange}
|
||||
setMode={setMode}
|
||||
onCancel={handleCancel}
|
||||
@ -318,16 +294,12 @@ type ExportContentProps = {
|
||||
range?: TimeRange;
|
||||
name: string;
|
||||
selectedCaseId?: string;
|
||||
singleNewCaseName: string;
|
||||
singleNewCaseDescription: string;
|
||||
activeTab: ExportTab;
|
||||
isStartingExport: boolean;
|
||||
onStartExport: () => Promise<boolean>;
|
||||
setActiveTab: (tab: ExportTab) => void;
|
||||
setName: (name: string) => void;
|
||||
setSelectedCaseId: (caseId: string | undefined) => void;
|
||||
setSingleNewCaseName: (name: string) => void;
|
||||
setSingleNewCaseDescription: (description: string) => void;
|
||||
setRange: (range: TimeRange | undefined) => void;
|
||||
setMode: (mode: ExportMode) => void;
|
||||
onCancel: () => void;
|
||||
@ -339,16 +311,12 @@ export function ExportContent({
|
||||
range,
|
||||
name,
|
||||
selectedCaseId,
|
||||
singleNewCaseName,
|
||||
singleNewCaseDescription,
|
||||
activeTab,
|
||||
isStartingExport,
|
||||
onStartExport,
|
||||
setActiveTab,
|
||||
setName,
|
||||
setSelectedCaseId,
|
||||
setSingleNewCaseName,
|
||||
setSingleNewCaseDescription,
|
||||
setRange,
|
||||
setMode,
|
||||
onCancel,
|
||||
@ -364,7 +332,7 @@ export function ExportContent({
|
||||
);
|
||||
const [selectedCameraIds, setSelectedCameraIds] = useState<string[]>([]);
|
||||
const [batchCaseSelection, setBatchCaseSelection] = useState<string>(
|
||||
selectedCaseId || "none",
|
||||
selectedCaseId || "new",
|
||||
);
|
||||
const [hasManualCameraSelection, setHasManualCameraSelection] =
|
||||
useState(false);
|
||||
@ -515,8 +483,7 @@ export function ExportContent({
|
||||
Boolean(range && range.before > range.after) &&
|
||||
selectedCameraCount > 0 &&
|
||||
!isStartingBatchExport &&
|
||||
(batchCaseSelection !== "new" || newCaseName.trim().length > 0) &&
|
||||
batchCaseSelection.length > 0;
|
||||
(batchCaseSelection !== "new" || newCaseName.trim().length > 0);
|
||||
|
||||
const onSelectTime = useCallback(
|
||||
(option: ExportOption) => {
|
||||
@ -600,7 +567,7 @@ export function ExportContent({
|
||||
})),
|
||||
};
|
||||
|
||||
if (isAdmin && batchCaseSelection !== "none") {
|
||||
if (isAdmin) {
|
||||
if (batchCaseSelection === "new") {
|
||||
payload.new_case_name = newCaseName.trim();
|
||||
payload.new_case_description = newCaseDescription.trim() || undefined;
|
||||
@ -819,30 +786,8 @@ export function ExportContent({
|
||||
{caseItem.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
<SelectSeparator />
|
||||
<SelectItem value="new">
|
||||
{t("export.case.newCaseOption")}
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
{selectedCaseId === "new" && (
|
||||
<div className="space-y-2 pt-1">
|
||||
<Input
|
||||
className="text-md"
|
||||
placeholder={t("export.case.newCaseNamePlaceholder")}
|
||||
value={singleNewCaseName}
|
||||
onChange={(e) => setSingleNewCaseName(e.target.value)}
|
||||
/>
|
||||
<Textarea
|
||||
className="text-md"
|
||||
placeholder={t("export.case.newCaseDescriptionPlaceholder")}
|
||||
value={singleNewCaseDescription}
|
||||
onChange={(e) =>
|
||||
setSingleNewCaseDescription(e.target.value)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</TabsContent>
|
||||
@ -1002,9 +947,6 @@ export function ExportContent({
|
||||
<SelectValue placeholder={t("export.case.placeholder")} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="none">
|
||||
{t("label.none", { ns: "common" })}
|
||||
</SelectItem>
|
||||
{cases
|
||||
?.sort((a, b) => a.name.localeCompare(b.name))
|
||||
.map((caseItem) => (
|
||||
|
||||
@ -115,8 +115,6 @@ export default function MobileReviewSettingsDrawer({
|
||||
const [selectedCaseId, setSelectedCaseId] = useState<string | undefined>(
|
||||
undefined,
|
||||
);
|
||||
const [singleNewCaseName, setSingleNewCaseName] = useState("");
|
||||
const [singleNewCaseDescription, setSingleNewCaseDescription] = useState("");
|
||||
const [isStartingExport, setIsStartingExport] = useState(false);
|
||||
const onStartExport = useCallback(async () => {
|
||||
if (isStartingExport) {
|
||||
@ -150,24 +148,12 @@ export default function MobileReviewSettingsDrawer({
|
||||
setIsStartingExport(true);
|
||||
|
||||
try {
|
||||
let exportCaseId: string | undefined = selectedCaseId;
|
||||
|
||||
if (selectedCaseId === "new" && singleNewCaseName.trim().length > 0) {
|
||||
const caseResp = await axios.post("cases", {
|
||||
name: singleNewCaseName.trim(),
|
||||
description: singleNewCaseDescription.trim() || undefined,
|
||||
});
|
||||
exportCaseId = caseResp.data?.id;
|
||||
} else if (selectedCaseId === "new" || selectedCaseId === "none") {
|
||||
exportCaseId = undefined;
|
||||
}
|
||||
|
||||
await axios.post<StartExportResponse>(
|
||||
`export/${camera}/start/${Math.round(range.after)}/end/${Math.round(range.before)}`,
|
||||
{
|
||||
source: "recordings",
|
||||
name,
|
||||
export_case_id: exportCaseId,
|
||||
export_case_id: selectedCaseId || undefined,
|
||||
},
|
||||
);
|
||||
|
||||
@ -183,8 +169,6 @@ export default function MobileReviewSettingsDrawer({
|
||||
});
|
||||
setName("");
|
||||
setSelectedCaseId(undefined);
|
||||
setSingleNewCaseName("");
|
||||
setSingleNewCaseDescription("");
|
||||
setRange(undefined);
|
||||
setMode("none");
|
||||
return true;
|
||||
@ -215,8 +199,6 @@ export default function MobileReviewSettingsDrawer({
|
||||
name,
|
||||
range,
|
||||
selectedCaseId,
|
||||
singleNewCaseDescription,
|
||||
singleNewCaseName,
|
||||
setRange,
|
||||
setMode,
|
||||
t,
|
||||
@ -379,16 +361,12 @@ export default function MobileReviewSettingsDrawer({
|
||||
range={range}
|
||||
name={name}
|
||||
selectedCaseId={selectedCaseId}
|
||||
singleNewCaseName={singleNewCaseName}
|
||||
singleNewCaseDescription={singleNewCaseDescription}
|
||||
activeTab={exportTab}
|
||||
isStartingExport={isStartingExport}
|
||||
onStartExport={onStartExport}
|
||||
setActiveTab={setExportTab}
|
||||
setName={setName}
|
||||
setSelectedCaseId={setSelectedCaseId}
|
||||
setSingleNewCaseName={setSingleNewCaseName}
|
||||
setSingleNewCaseDescription={setSingleNewCaseDescription}
|
||||
setRange={setRange}
|
||||
setMode={(mode) => {
|
||||
setMode(mode);
|
||||
@ -401,8 +379,6 @@ export default function MobileReviewSettingsDrawer({
|
||||
setMode("none");
|
||||
setRange(undefined);
|
||||
setSelectedCaseId(undefined);
|
||||
setSingleNewCaseName("");
|
||||
setSingleNewCaseDescription("");
|
||||
setExportTab("export");
|
||||
setDrawerMode("select");
|
||||
}}
|
||||
|
||||
@ -55,7 +55,6 @@ type MultiExportDialogProps = {
|
||||
children: React.ReactNode;
|
||||
};
|
||||
|
||||
const NONE_CASE_OPTION = "none";
|
||||
const NEW_CASE_OPTION = "new";
|
||||
|
||||
export default function MultiExportDialog({
|
||||
@ -75,7 +74,10 @@ export default function MultiExportDialog({
|
||||
const { data: cases } = useSWR<ExportCase[]>(isAdmin ? "cases" : null);
|
||||
|
||||
const [open, setOpen] = useState(false);
|
||||
const [caseSelection, setCaseSelection] = useState<string>(NONE_CASE_OPTION);
|
||||
// Single unified state: either NEW_CASE_OPTION or an existing case id.
|
||||
// Defaults to NEW_CASE_OPTION, which is also the only valid value for
|
||||
// non-admins since they can't attach to existing cases.
|
||||
const [caseSelection, setCaseSelection] = useState<string>(NEW_CASE_OPTION);
|
||||
const [newCaseName, setNewCaseName] = useState("");
|
||||
const [newCaseDescription, setNewCaseDescription] = useState("");
|
||||
const [isExporting, setIsExporting] = useState(false);
|
||||
@ -132,7 +134,7 @@ export default function MultiExportDialog({
|
||||
}, [t, locale]);
|
||||
|
||||
const resetState = useCallback(() => {
|
||||
setCaseSelection(NONE_CASE_OPTION);
|
||||
setCaseSelection(NEW_CASE_OPTION);
|
||||
setNewCaseName("");
|
||||
setNewCaseDescription("");
|
||||
setIsExporting(false);
|
||||
@ -144,7 +146,7 @@ export default function MultiExportDialog({
|
||||
resetState();
|
||||
} else {
|
||||
// Freshly reset each time so the default name reflects "now"
|
||||
setCaseSelection(NONE_CASE_OPTION);
|
||||
setCaseSelection(NEW_CASE_OPTION);
|
||||
setNewCaseName(defaultCaseName);
|
||||
setNewCaseDescription("");
|
||||
setIsExporting(false);
|
||||
@ -183,7 +185,7 @@ export default function MultiExportDialog({
|
||||
|
||||
const payload: BatchExportBody = { items };
|
||||
|
||||
if (isAdmin && caseSelection !== NONE_CASE_OPTION) {
|
||||
if (isAdmin) {
|
||||
if (isNewCase) {
|
||||
payload.new_case_name = newCaseName.trim();
|
||||
payload.new_case_description = newCaseDescription.trim() || undefined;
|
||||
@ -321,15 +323,12 @@ export default function MultiExportDialog({
|
||||
<SelectValue placeholder={t("export.case.placeholder")} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value={NONE_CASE_OPTION}>
|
||||
{t("label.none", { ns: "common" })}
|
||||
</SelectItem>
|
||||
{existingCases.map((caseItem) => (
|
||||
<SelectItem key={caseItem.id} value={caseItem.id}>
|
||||
{caseItem.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
<SelectSeparator />
|
||||
{existingCases.length > 0 && <SelectSeparator />}
|
||||
<SelectItem value={NEW_CASE_OPTION}>
|
||||
{t("export.case.newCaseOption")}
|
||||
</SelectItem>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user