feat: catalog audit can repair orphaned refs and duplicate classnames
- add repairOrphanedCatalog: remove catalog_items rows whose item_ids only reference missing items_base entries, strip orphaned ids from mixed rows - add repairDuplicateClassnames: merge items_base duplicates into one canonical row per classname, remap references, delete duplicate rows - add 'repair structural issues' checkbox + result display in audit UI
This commit is contained in:
1 parent
bee55e1fd4
commit
6fc14b9b84
4 files changed
+384
-1
No files matched your search
@@ -48,6 +48,11 @@ interface AuditSummary {
|
||||
catalogSqlApplied: number;
|
||||
furniDataAdded: number;
|
||||
furniDataDuplicatesRemoved: number;
|
||||
orphanedRemoved: number;
|
||||
orphanedCleaned: number;
|
||||
duplicatesMerged: number;
|
||||
duplicateRowsRemoved: number;
|
||||
remappedReferences: number;
|
||||
}
|
||||
|
||||
interface RepairStats {
|
||||
@@ -73,6 +78,13 @@ interface AuditEvent {
|
||||
applied?: number;
|
||||
sqlFailed?: number;
|
||||
furniData?: { added: number; removedDuplicates: number };
|
||||
structure?: {
|
||||
orphanedRemoved: number;
|
||||
orphanedCleaned: number;
|
||||
duplicatesMerged: number;
|
||||
duplicateRowsRemoved: number;
|
||||
remappedReferences: number;
|
||||
};
|
||||
}
|
||||
|
||||
const TYPE_LABELS: Record<string, string> = {
|
||||
@@ -131,6 +143,7 @@ export function AuditClient() {
|
||||
const [generateSql, setGenerateSql] = useState(false);
|
||||
const [applySql, setApplySql] = useState(false);
|
||||
const [repairFurniData, setRepairFurniData] = useState(false);
|
||||
const [repairStructure, setRepairStructure] = useState(false);
|
||||
|
||||
const [issues, setIssues] = useState<AuditIssue[]>([]);
|
||||
const [missingFromSources, setMissingFromSources] = useState<
|
||||
@@ -150,6 +163,13 @@ export function AuditClient() {
|
||||
added: number;
|
||||
removedDuplicates: number;
|
||||
} | null>(null);
|
||||
const [structureResult, setStructureResult] = useState<{
|
||||
orphanedRemoved: number;
|
||||
orphanedCleaned: number;
|
||||
duplicatesMerged: number;
|
||||
duplicateRowsRemoved: number;
|
||||
remappedReferences: number;
|
||||
} | null>(null);
|
||||
|
||||
const [checkProgress, setCheckProgress] = useState<{
|
||||
section: string;
|
||||
@@ -170,6 +190,7 @@ export function AuditClient() {
|
||||
setSqlText(null);
|
||||
setSqlApplied(null);
|
||||
setFurniDataResult(null);
|
||||
setStructureResult(null);
|
||||
setCheckProgress(null);
|
||||
setActiveTab("issues");
|
||||
|
||||
@@ -187,6 +208,7 @@ export function AuditClient() {
|
||||
sql: generateSql || applySql,
|
||||
applySql,
|
||||
repairFurniData,
|
||||
repairStructure,
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -267,6 +289,10 @@ export function AuditClient() {
|
||||
if (evt.furniData) setFurniDataResult(evt.furniData);
|
||||
}
|
||||
|
||||
if (evt.type === "structure_repair_complete") {
|
||||
if (evt.structure) setStructureResult(evt.structure);
|
||||
}
|
||||
|
||||
if (evt.type === "batch_complete") {
|
||||
setIssues(evt.issues ?? []);
|
||||
setMissingFromSources(evt.missingFromSources ?? []);
|
||||
@@ -309,7 +335,14 @@ export function AuditClient() {
|
||||
setLoading(false);
|
||||
abortRef.current = null;
|
||||
}
|
||||
}, [repairEnabled, repairNitros, generateSql, applySql, repairFurniData]);
|
||||
}, [
|
||||
repairEnabled,
|
||||
repairNitros,
|
||||
generateSql,
|
||||
applySql,
|
||||
repairFurniData,
|
||||
repairStructure,
|
||||
]);
|
||||
|
||||
const errors = issues.filter((i) => i.severity === "error");
|
||||
const warnings = issues.filter((i) => i.severity === "warning");
|
||||
@@ -372,6 +405,13 @@ export function AuditClient() {
|
||||
>
|
||||
Repair FurnitureData.json (add missing + dedupe)
|
||||
</AuditCheckbox>
|
||||
<AuditCheckbox
|
||||
checked={repairStructure}
|
||||
disabled={loading}
|
||||
onChange={setRepairStructure}
|
||||
>
|
||||
Repair structural issues (orphaned refs + duplicates)
|
||||
</AuditCheckbox>
|
||||
{loading && (
|
||||
<Button
|
||||
variant="outline"
|
||||
@@ -601,6 +641,49 @@ export function AuditClient() {
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
{structureResult && (
|
||||
<div className="flex items-center gap-2 col-span-full">
|
||||
<span className="text-xs text-muted-foreground">
|
||||
Structural repair:{" "}
|
||||
{structureResult.orphanedRemoved > 0 && (
|
||||
<>
|
||||
<span className="text-[var(--admin-success)] font-medium">
|
||||
{structureResult.orphanedRemoved} orphaned rows removed
|
||||
</span>
|
||||
{" · "}
|
||||
</>
|
||||
)}
|
||||
{structureResult.orphanedCleaned > 0 && (
|
||||
<>
|
||||
<span className="font-medium">
|
||||
{structureResult.orphanedCleaned} cleaned
|
||||
</span>
|
||||
{" · "}
|
||||
</>
|
||||
)}
|
||||
{structureResult.duplicatesMerged > 0 && (
|
||||
<>
|
||||
<span className="text-[var(--admin-success)] font-medium">
|
||||
{structureResult.duplicatesMerged} classname(s) merged
|
||||
</span>
|
||||
{" · "}
|
||||
</>
|
||||
)}
|
||||
{structureResult.duplicateRowsRemoved > 0 && (
|
||||
<>
|
||||
<span className="text-[var(--admin-warning)] font-medium">
|
||||
{structureResult.duplicateRowsRemoved} duplicate rows
|
||||
removed
|
||||
</span>
|
||||
{" · "}
|
||||
</>
|
||||
)}
|
||||
<span className="text-[var(--admin-text-muted)]">
|
||||
{structureResult.remappedReferences} reference(s) remapped
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Tabs */}
|
||||
@@ -733,6 +816,7 @@ export function AuditClient() {
|
||||
setSqlText(null);
|
||||
setSqlApplied(null);
|
||||
setFurniDataResult(null);
|
||||
setStructureResult(null);
|
||||
setProgress("");
|
||||
setCheckProgress(null);
|
||||
}}
|
||||
|
||||
Reference in new issue
Block a user