fix(studio): harden organize-imports against long-running queries and hangs
The original GET route used a correlated NOT EXISTS / FIND_IN_SET subquery over the entire catalog_items table for every recent import audit entry, causing server timeouts when the audit log or catalog grew large. The per-item host-page validation inside the create action also issued one SELECT + one UPDATE per moved offer. Changes: - GET /api/admin/import/organize: replace the correlated subquery with a bounded candidate list and a JS-side placed-set check, then resolve all needed base items in a single indexed SELECT. This bounds the query cost regardless of catalog or audit log size. - organizeImportFurni action: validate mover ids in one SELECT, then batch every move per group into a single UPDATE with a CASE expression instead of one UPDATE per item. - OrganizeImportsDialog: add a 45-second abort timeout on the fetch and a distinct load-error state so the UI never silently hangs. - Add 'loadError' translation key (en + nl).
This commit is contained in:
1 parent
01a85ebcd0
commit
19dc0347df
5 files changed
+181
-66
No files matched your search
@@ -75,6 +75,7 @@ export function OrganizeImportsDialog({
|
||||
const router = useRouter();
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [loadError, setLoadError] = useState(false);
|
||||
const [items, setItems] = useState<OrganizeItem[]>([]);
|
||||
const [rootPages, setRootPages] = useState<RootPage[]>([]);
|
||||
const [importRootPageId, setImportRootPageId] = useState<number | null>(null);
|
||||
@@ -96,10 +97,17 @@ export function OrganizeImportsDialog({
|
||||
|
||||
const load = useCallback(async () => {
|
||||
setLoading(true);
|
||||
setLoadError(false);
|
||||
const controller = new AbortController();
|
||||
const timeout = setTimeout(() => controller.abort(), 45000);
|
||||
try {
|
||||
const [data, tree] = await Promise.all([
|
||||
fetch("/api/admin/import/organize").then((r) => r.json()),
|
||||
fetch("/api/admin/catalog/tree?parentId=-1").then((r) => r.json()),
|
||||
fetch("/api/admin/import/organize", { signal: controller.signal }).then(
|
||||
(r) => r.json(),
|
||||
),
|
||||
fetch("/api/admin/catalog/tree?parentId=-1", {
|
||||
signal: controller.signal,
|
||||
}).then((r) => r.json()),
|
||||
]);
|
||||
setItems(Array.isArray(data?.items) ? data.items : []);
|
||||
setImportRootPageId(data?.importRootPageId ?? null);
|
||||
@@ -113,8 +121,10 @@ export function OrganizeImportsDialog({
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("[OrganizeImports] load failed:", error);
|
||||
toast.error("Failed to load imports");
|
||||
setLoadError(true);
|
||||
setItems([]);
|
||||
} finally {
|
||||
clearTimeout(timeout);
|
||||
setLoading(false);
|
||||
}
|
||||
}, []);
|
||||
@@ -347,7 +357,7 @@ export function OrganizeImportsDialog({
|
||||
</div>
|
||||
) : items.length === 0 ? (
|
||||
<div className="flex flex-1 flex-col items-center justify-center gap-3 text-sm text-muted-foreground">
|
||||
{t("empty")}
|
||||
{loadError ? t("loadError") : t("empty")}
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
|
||||
Reference in new issue
Block a user