feat: refine metadata catalog workflows

This commit is contained in:
Codex
2026-09-02 11:38:47 +02:00
parent 076c9742c5
commit 4531746038
48 changed files with 1345 additions and 493 deletions
@@ -197,7 +197,6 @@ function registerCompletedSyncRun(run: CatalogSyncRun) {
const synchronizationScopes = [
{ scope: "tables", label: "Synchronize tables" },
{ scope: "columns", label: "Synchronize all columns" },
{ scope: "relationships", label: "Synchronize relationships" },
{ scope: "all", label: "Synchronize all" },
] as const;
@@ -234,9 +233,7 @@ test("renders Fleet Ledger with real metrics, conceptual row tooltips, and persi
expect(screen.queryByText("Inspect physical metadata, curate descriptions and control catalog operations.")).not.toBeInTheDocument();
expect(screen.queryByRole("button", { name: "Back to workspace" })).not.toBeInTheDocument();
expect(screen.queryByRole("button", { name: /Add database/i })).not.toBeInTheDocument();
expect(screen.getByRole("columnheader", { name: /Revision \/ Evidence/ })).toBeVisible();
expect(screen.getByRole("columnheader", { name: /NL→SQL runtime/ })).toBeVisible();
expect(screen.getByRole("columnheader", { name: /Metadata Catalog/ })).toBeVisible();
expect(screen.getByRole("columnheader", { name: /Catalog status/ })).toBeVisible();
const summary = screen.getByRole("region", { name: "Fleet summary" });
expect(await within(summary).findByText("2,275")).toBeVisible();
expect(within(summary).getByText("75%")).toBeVisible();
@@ -261,13 +258,14 @@ test("renders Fleet Ledger with real metrics, conceptual row tooltips, and persi
await user.click(within(restoredDatabaseRow).getByRole("checkbox", { name: /toggle row selection/i }));
const actionPicker = screen.getByRole("combobox", { name: "Batch action" });
expect(within(actionPicker).getByRole("option", { name: /^Synchronize all(?:,|$)/ })).toBeVisible();
expect(within(actionPicker).queryByRole("option", { name: "Synchronize all columns" })).not.toBeInTheDocument();
await user.selectOptions(actionPicker, "generate-missing");
expect(screen.getAllByRole("combobox", { name: "Metadata-generation LLM model" })).toHaveLength(1);
expect(modelSelector).toHaveValue("local-qwen");
});
test("shows repository, NL→SQL runtime, and Metadata Catalog states independently", async () => {
test("shows synchronization, description, and sensitive-data states independently", async () => {
const configured = makeDatabase({ connectionStatus: "reachable", testedVersion: 3 });
const needsRuntimeConfiguration = makeDatabase({
...unconfigured,
@@ -281,21 +279,58 @@ test("shows repository, NL→SQL runtime, and Metadata Catalog states independen
renderPage({ rows: [configured, needsRuntimeConfiguration, orphan], presentation: "fleet" });
const configuredRow = await screen.findByRole("row", { name: /Policlinico San Donato/ });
expect(within(configuredRow).getByText("Active revision aaaaaaa")).toBeVisible();
expect(within(configuredRow).getByText("Evidence materialized · filesystem")).toBeVisible();
expect(within(configuredRow).getByText("Ready")).toBeVisible();
expect(within(configuredRow).getByText("Configured")).toBeVisible();
expect(within(configuredRow).getByText("Connection reachable")).toBeVisible();
expect(within(configuredRow).getByText("Not synchronized")).toBeVisible();
expect(within(configuredRow).getByText(/Descriptions ·/)).toBeVisible();
expect(within(configuredRow).getByText(/Sensitive ·/)).toBeVisible();
const needsConfigurationRow = screen.getByRole("row", { name: /Research laboratory/ });
expect(within(needsConfigurationRow).getByText("Evidence credentials required · http")).toBeVisible();
expect(within(needsConfigurationRow).getByText("Configuration required")).toBeVisible();
expect(within(needsConfigurationRow).getByText("Not configured")).toBeVisible();
expect(within(needsConfigurationRow).getByText("Not synchronized")).toBeVisible();
expect(within(needsConfigurationRow).getByText(/Descriptions ·/)).toBeVisible();
const orphanRow = screen.getByRole("row", { name: /Retired workspace/ });
expect(within(orphanRow).getByText("Workspace missing")).toBeVisible();
expect(within(orphanRow).getByText("Unavailable")).toBeVisible();
expect(within(orphanRow).getByText("Orphaned configuration")).toBeVisible();
expect(within(orphanRow).getByText("Not synchronized")).toBeVisible();
});
test("shows the configured access type and endpoint in catalog status", async () => {
const direct = makeDatabase();
const rest = makeDatabase({
id: "33333333-3333-4333-8333-333333333333",
workspaceId: "rest-lab",
workspaceName: "REST laboratory",
binding: {
transport: "rest_api",
baseUrl: "https://203.0.113.18/dwh/",
restPath: "/health",
restAuth: "x-api-key",
},
});
const ssh = makeDatabase({
id: "44444444-4444-4444-8444-444444444444",
workspaceId: "ssh-lab",
workspaceName: "SSH laboratory",
binding: {
transport: "ssh_tunnel",
sshHost: "bastion.example.test",
sshPort: 2222,
sshUsername: "operator",
sshTargetHost: "postgres.internal",
sshTargetPort: 5432,
username: "reader",
},
});
renderPage({ rows: [direct, rest, ssh], presentation: "fleet" });
const directRow = await screen.findByRole("row", { name: /Policlinico San Donato/ });
expect(within(directRow).getByText("Direct PostgreSQL")).toBeVisible();
expect(within(directRow).getByText("db.internal:5432")).toBeVisible();
const restRow = screen.getByRole("row", { name: /REST laboratory/ });
expect(within(restRow).getByText("REST")).toBeVisible();
expect(within(restRow).getByText("203.0.113.18")).toBeVisible();
const sshRow = screen.getByRole("row", { name: /SSH laboratory/ });
expect(within(sshRow).getByText("SSH")).toBeVisible();
expect(within(sshRow).getByText("bastion.example.test:2222")).toBeVisible();
});
test("keeps Fleet Ledger visible when a catalog response omits the Evidence projection", async () => {
@@ -306,7 +341,7 @@ test("keeps Fleet Ledger visible when a catalog response omits the Evidence proj
expect(await screen.findByRole("heading", { name: "Database management" })).toBeVisible();
const databaseRow = await screen.findByRole("row", { name: /Policlinico San Donato/ });
expect(within(databaseRow).getByText("Evidence state unavailable")).toBeVisible();
expect(within(databaseRow).getByText("Not synchronized")).toBeVisible();
});
test("opens the relationship map directly from a Fleet database and restores focus on return", async () => {
@@ -359,7 +394,7 @@ test("offers catalog configuration directly on an unconfigured Fleet workspace",
expect(within(drawer).getByLabelText("Workspace")).toHaveValue("Research laboratory");
});
test("keeps only the local back action while browsing Fleet Ledger columns", async () => {
test("keeps the table back action in the Fleet Ledger breadcrumb while browsing columns", async () => {
const user = userEvent.setup();
server.use(
http.get("/api/catalog/databases/:databaseId/tables", () => HttpResponse.json([patientsTable])),
@@ -372,9 +407,17 @@ test("keeps only the local back action while browsing Fleet Ledger columns", asy
name: "View tables for Policlinico San Donato",
}));
expect(await screen.findByRole("button", { name: "Back to databases" })).toBeVisible();
expect(screen.getByRole("combobox", { name: "Table action" })).toBeDisabled();
expect(screen.queryByRole("button", { name: "Sync history" })).not.toBeInTheDocument();
expect(screen.queryByRole("button", { name: "Description history" })).not.toBeInTheDocument();
expect(screen.queryByRole("button", { name: "Sensitive history" })).not.toBeInTheDocument();
await user.click(await screen.findByRole("button", { name: "View columns for patients" }));
expect(await screen.findByRole("button", { name: "Back to tables" })).toBeVisible();
const tableBackAction = await screen.findByRole("button", { name: "Back to tables" });
expect(tableBackAction).toBeVisible();
expect(screen.getByRole("combobox", { name: "Column action" })).toBeDisabled();
expect(screen.getByRole("navigation", { name: "Database hierarchy" }).parentElement)
.toContainElement(tableBackAction);
await waitFor(() => {
expect(screen.queryByRole("button", { name: "Back to databases" })).not.toBeInTheDocument();
});
@@ -427,7 +470,7 @@ test("reopens database synchronization history when no run is active", async ()
renderPage({ rows: [makeDatabase()], presentation: "fleet" });
const databaseRow = await screen.findByRole("row", { name: /Policlinico San Donato/ });
await user.click(within(databaseRow).getByRole("button", { name: "View synchronization history for Policlinico San Donato" }));
await user.click(within(databaseRow).getByText("Policlinico San Donato"));
const drawer = await screen.findByRole("dialog", { name: "Schema synchronization" });
expect(drawer).toBeVisible();
@@ -628,6 +671,7 @@ test.each(synchronizationScopes)(
await user.click(screen.getByRole("menuitem", { name: label }));
await waitFor(() => expect(startBody).toEqual({ version: 3, scope, tableIds: [] }));
expect(screen.getByText("1 selected")).toBeVisible();
},
);
@@ -1001,15 +1045,16 @@ test("selected database Actions confirms and deletes catalog tables for the full
],
target: "tables",
}));
await waitFor(() => expect(screen.queryByText("2 selected")).not.toBeInTheDocument());
await waitFor(() => expect(screen.getByRole("textbox", { name: "Search databases" })).toHaveFocus());
expect(screen.getByText("2 selected")).toBeVisible();
await user.click(screen.getByRole("button", { name: "Clear" }));
expect(screen.queryByText("2 selected")).not.toBeInTheDocument();
});
test("presents completed synchronization steps as success and skips unneeded confirmation", async () => {
test("presents completed database synchronization steps as success and skips unneeded confirmation", async () => {
const user = userEvent.setup();
const run = {
...makeSyncRun("columns"),
counts: { tables: 163, columns: 2_275 },
...makeSyncRun("all"),
counts: { tables: 163, columns: 2_275, relationships: 18 },
};
registerCompletedSyncRun(run);
server.use(http.post("/api/catalog/databases/:databaseId/sync-runs", () => (
@@ -1022,7 +1067,7 @@ test("presents completed synchronization steps as success and skips unneeded con
const databaseRow = await screen.findByRole("row", { name: /Policlinico San Donato/ });
await user.click(within(databaseRow).getByRole("checkbox", { name: /toggle row selection/i }));
await user.click(screen.getByRole("button", { name: "Actions" }));
await user.click(await screen.findByRole("menuitem", { name: "Synchronize all columns" }));
await user.click(await screen.findByRole("menuitem", { name: "Synchronize all" }));
expect(await screen.findByRole("heading", { name: "Succeeded" })).toBeVisible();
const steps = screen.getByRole("list", { name: "Synchronization steps" });
@@ -1039,7 +1084,7 @@ test("presents completed synchronization steps as success and skips unneeded con
expect(steps.querySelectorAll(".text-primary")).toHaveLength(0);
});
test("database Overview exposes every synchronization scope", async () => {
test("database Overview exposes the database synchronization scopes", async () => {
const user = userEvent.setup();
let startBody: unknown;
const run = makeSyncRun("relationships");
@@ -1520,6 +1565,37 @@ test("starts description generation for multiple selected tables", async () => {
expect(screen.getByText("2 selected")).toBeVisible();
});
test("generates descriptions for every column in selected tables", async () => {
const user = userEvent.setup();
const queuedRun = makeDescriptionGenerationRun({ scope: "selected_columns", total: 1 });
let startBody: unknown;
server.use(
http.get("/api/catalog/metadata-generation/models", () => HttpResponse.json({ models: [{ id: "local-qwen", label: "Local Qwen" }], default: "local-qwen" })),
http.get("/api/catalog/databases/:databaseId/tables", () => HttpResponse.json([patientsTable])),
http.get(`/api/catalog/databases/:databaseId/tables/${patientsTable.id}/columns`, () => HttpResponse.json([patientIdColumn])),
http.post("/api/catalog/databases/:databaseId/description-generation-runs", async ({ request }) => {
startBody = await request.json();
return HttpResponse.json(queuedRun, { status: 202 });
}),
http.get("/api/catalog/description-generation-runs/:runId", () => HttpResponse.json(queuedRun)),
http.get("/api/catalog/description-generation-runs/:runId/events-list", () => HttpResponse.json([])),
);
renderPage({ rows: [makeDatabase({ connectionStatus: "reachable", testedVersion: 3 })] });
await user.click(await screen.findByRole("button", { name: "View Policlinico San Donato" }));
await user.click(screen.getByRole("tab", { name: "Tables" }));
await user.click(within(await screen.findByRole("row", { name: /patients/ })).getByRole("checkbox"));
await user.click(screen.getByRole("button", { name: "Actions" }));
await user.click(await screen.findByRole("menuitem", { name: "Generate descriptions for all columns" }));
await waitFor(() => expect(startBody).toEqual({
modelId: "local-qwen",
scope: "selected_columns",
targetIds: [patientIdColumn.id],
}));
expect(await screen.findByText("Column description generation started for 1 column")).toBeVisible();
});
test("observes an active run from another browser and reopens a terminal run from history", async () => {
const user = userEvent.setup();
const activeRun = makeDescriptionGenerationRun({
@@ -1913,6 +1989,12 @@ test("starts one selected column with the configured default model", async () =>
await user.click(await screen.findByRole("button", { name: "View Policlinico San Donato" }));
await user.click(screen.getByRole("tab", { name: "Tables" }));
await user.click(await screen.findByRole("button", { name: "View columns for patients" }));
expect(screen.queryByRole("button", { name: "Sync columns" })).not.toBeInTheDocument();
expect(screen.getByRole("columnheader", { name: /Select/i })).toBeVisible();
expect(await screen.findByRole("checkbox", { name: "Sensitive data for id" })).toHaveAttribute(
"title",
"Mark this column as sensitive; this does not select it for actions.",
);
const columnRow = (await screen.findAllByRole("row", { name: /Patient identifier/ }))
.find((row) => within(row).queryByRole("checkbox", { name: /toggle row selection/i }));
expect(columnRow).toBeDefined();