feat: complete catalog-driven preprocessing
Publish documentation / publish (push) Successful in 2m12s

This commit is contained in:
Codex
2026-09-06 17:49:35 +02:00
parent 8707ae1d46
commit cffa60772e
141 changed files with 5898 additions and 3015 deletions
@@ -50,7 +50,7 @@ interface DatabaseGridProps {
rows: CatalogDatabase[],
scope: Extract<DescriptionGenerationScope, "all" | "missing">,
) => Promise<void>;
onConsolidateColumnDescriptions: (rows: CatalogDatabase[]) => Promise<void>;
onConsolidateDescriptions: (rows: CatalogDatabase[]) => Promise<void>;
onSuggestSensitive: (rows: CatalogDatabase[]) => Promise<void>;
onDeleteMetadataSelected: (
rows: CatalogDatabase[],
@@ -78,7 +78,7 @@ type DatabaseFleetAction =
| "sync-all"
| "generate-all"
| "generate-missing"
| "consolidate-columns"
| "consolidate-descriptions"
| "suggest-sensitive"
| "clear-tables"
| "clear-relationships";
@@ -341,7 +341,7 @@ export function DatabaseGrid({
descriptionGenerationActive,
sensitivityAnalysisRuns = [],
onGenerateDescriptions,
onConsolidateColumnDescriptions,
onConsolidateDescriptions,
onSuggestSensitive,
onDeleteMetadataSelected,
onRefresh,
@@ -435,7 +435,7 @@ export function DatabaseGrid({
&& Boolean(selectedMetadataModel)
&& !descriptionGenerationActive
&& selectedRows.every((row) => row.configured && row.id && !row.activeSyncRun);
const canConsolidateColumnDescriptions = canManage
const canConsolidateDescriptions = canManage
&& selectedRows.length === 1
&& !descriptionGenerationActive
&& selectedRows.every((row) => row.configured && row.id && !row.activeSyncRun);
@@ -490,13 +490,13 @@ export function DatabaseGrid({
{ id: "sync-all", label: "Synchronize all", group: "Synchronization", disabled: !canSyncSelection, disabledReason: syncReason },
{ id: "generate-missing", label: "Generate missing descriptions", group: "Descriptions", disabled: !canGenerateDescriptions, disabledReason: generationReason },
{ id: "generate-all", label: "Generate all descriptions", group: "Descriptions", disabled: !canGenerateDescriptions, disabledReason: generationReason, runLabel: "Review generation" },
{ id: "consolidate-columns", label: "Copy generated descriptions to all columns", group: "Descriptions", disabled: !canConsolidateColumnDescriptions, disabledReason: consolidationReason, runLabel: "Review copy" },
{ id: "consolidate-descriptions", label: "Copy generated description to descriptions", group: "Descriptions", disabled: !canConsolidateDescriptions, disabledReason: consolidationReason, runLabel: "Review copy" },
{ id: "suggest-sensitive", label: "Analyze sensitive fields", group: "Sensitive data", disabled: !canSuggestSensitive, disabledReason: sensitiveReason, runLabel: "Open review" },
{ id: "clear-tables", label: "Clear catalog tables and relationships", group: "Cleanup", disabled: !canDeleteMetadataSelection, disabledReason: cleanupReason, tone: "destructive", runLabel: "Review cleanup" },
{ id: "clear-relationships", label: "Clear catalog relationships", group: "Cleanup", disabled: !canDeleteMetadataSelection, disabledReason: cleanupReason, tone: "destructive", runLabel: "Review cleanup" },
];
}, [
canConsolidateColumnDescriptions,
canConsolidateDescriptions,
canDeleteMetadataSelection,
canGenerateDescriptions,
canManage,
@@ -569,7 +569,7 @@ export function DatabaseGrid({
setPendingDelete(selectedAction === "clear-tables" ? "tables" : "relationships");
return;
}
if (selectedAction === "consolidate-columns") {
if (selectedAction === "consolidate-descriptions") {
setPendingConsolidation(true);
return;
}
@@ -652,10 +652,10 @@ export function DatabaseGrid({
>
<div className="mr-auto min-w-56">
<p id="database-consolidation-confirmation" className="text-sm font-semibold">
Copy generated descriptions to all columns in {selectedRows[0]?.workspaceName}?
Copy generated descriptions to Description fields in {selectedRows[0]?.workspaceName}?
</p>
<p className="text-xs text-muted-foreground">
Every non-empty AI-generated column description will replace the current Description value. Generated descriptions are retained.
Every non-empty AI-generated table and column description will replace its current Description value. Generated descriptions are retained.
</p>
</div>
<Button type="button" variant="ghost" disabled={action !== null} onClick={closeConsolidationConfirmation}>Cancel</Button>
@@ -666,10 +666,10 @@ export function DatabaseGrid({
disabled={action !== null}
onClick={() => void perform(
"consolidate",
() => onConsolidateColumnDescriptions(selectedRows),
() => onConsolidateDescriptions(selectedRows),
)}
>
Copy to all columns
Copy to descriptions
</Button>
</div>
) : pendingDelete ? (
@@ -760,10 +760,10 @@ export function DatabaseGrid({
</Menu.Item>
<Menu.Item
className="rounded-md px-3 py-2 text-sm outline-none data-[highlighted]:bg-muted data-[disabled]:opacity-45"
disabled={!canConsolidateColumnDescriptions}
disabled={!canConsolidateDescriptions}
onClick={() => setPendingConsolidation(true)}
>
Copy generated descriptions to all columns
Copy generated description to descriptions
</Menu.Item>
<Menu.Separator className="my-1 h-px bg-border" />
<Menu.Item
@@ -103,7 +103,7 @@ test("replays ordered SSE events, reconnects from the latest sequence, and keeps
http.get("/api/catalog/description-generation-runs/:runId/events-list", () => {
eventReads += 1;
return HttpResponse.json(eventReads === 1 ? [event(1, "Run queued")] : [
event(2, "First description saved"),
event(2, 'Generated description for Column "patients.birth_date".'),
event(3, "Polling recovered this event"),
]);
}),
@@ -127,11 +127,20 @@ test("replays ordered SSE events, reconnects from the latest sequence, and keeps
logHeight = 200;
act(() => {
FakeEventSource.instances[0].emitNamed("log", event(2, "First description saved"), "2");
FakeEventSource.instances[0].emitNamed("log", event(2, "First description saved"), "2");
FakeEventSource.instances[0].emitNamed(
"log",
event(2, 'Generated description for Column "patients.birth_date".'),
"2",
);
FakeEventSource.instances[0].emitNamed(
"log",
event(2, 'Generated description for Column "patients.birth_date".'),
"2",
);
FakeEventSource.instances[0].onerror?.();
});
await waitFor(() => expect(log.scrollTop).toBe(log.scrollHeight));
expect(within(log).getByText('Generated description for Column "patients.birth_date".')).toBeVisible();
logHeight = 300;
expect(await within(log).findByText("Polling recovered this event")).toBeVisible();
@@ -154,7 +163,9 @@ test("replays ordered SSE events, reconnects from the latest sequence, and keeps
FakeEventSource.instances[1].emitNamed("log", event(4, "Replay continued in order"), "4");
});
await waitFor(() => expect(within(log).getAllByText("First description saved")).toHaveLength(1));
await waitFor(() => expect(
within(log).getAllByText('Generated description for Column "patients.birth_date".'),
).toHaveLength(1));
expect(within(log).getAllByText("Polling recovered this event")).toHaveLength(1);
expect(within(log).getByText("Replay continued in order")).toBeVisible();
expect(log.scrollTop).toBe(0);
@@ -162,11 +173,11 @@ test("replays ordered SSE events, reconnects from the latest sequence, and keeps
expect(log.scrollTop).toBe(log.scrollHeight);
expect(screen.queryByRole("button", { name: "Jump to latest" })).not.toBeInTheDocument();
expect(log.textContent!.indexOf("Run queued")).toBeLessThan(
log.textContent!.indexOf("First description saved"),
);
expect(log.textContent!.indexOf("First description saved")).toBeLessThan(
log.textContent!.indexOf("Polling recovered this event"),
log.textContent!.indexOf('Generated description for Column "patients.birth_date".'),
);
expect(
log.textContent!.indexOf('Generated description for Column "patients.birth_date".'),
).toBeLessThan(log.textContent!.indexOf("Polling recovered this event"));
});
test("stops queued or running work without hiding earlier results or events", async () => {