feat: refine metadata catalog workflows
This commit is contained in:
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user