feat: complete catalog fleet management workflow
This commit is contained in:
@@ -9,6 +9,7 @@ import type {
|
||||
CatalogSyncRun,
|
||||
CatalogTable,
|
||||
DescriptionGenerationRun,
|
||||
SensitiveDataSuggestionRun,
|
||||
} from "../api/catalog-databases";
|
||||
import { Toaster } from "../components/ui/sonner";
|
||||
import { DatabaseManagementPage } from "./DatabaseManagementPage";
|
||||
@@ -72,11 +73,13 @@ function renderPage({
|
||||
canManage = true,
|
||||
canManageSecrets = true,
|
||||
onNavigationStateChange,
|
||||
presentation = "legacy",
|
||||
}: {
|
||||
rows?: CatalogDatabase[] | (() => CatalogDatabase[]);
|
||||
canManage?: boolean;
|
||||
canManageSecrets?: boolean;
|
||||
onNavigationStateChange?: (state: { dirty: boolean; busy: boolean }) => void;
|
||||
presentation?: "legacy" | "fleet";
|
||||
} = {}) {
|
||||
server.use(http.get("/api/catalog/databases", () => HttpResponse.json(
|
||||
typeof rows === "function" ? rows() : rows,
|
||||
@@ -88,6 +91,7 @@ function renderPage({
|
||||
canManage={canManage}
|
||||
canManageSecrets={canManageSecrets}
|
||||
onNavigationStateChange={onNavigationStateChange}
|
||||
presentation={presentation}
|
||||
/>
|
||||
<Toaster duration={Infinity} />
|
||||
</QueryClientProvider>,
|
||||
@@ -145,6 +149,27 @@ function makeDescriptionGenerationRun(
|
||||
};
|
||||
}
|
||||
|
||||
function makeSensitiveDataSuggestionRun(
|
||||
overrides: Partial<SensitiveDataSuggestionRun> = {},
|
||||
): SensitiveDataSuggestionRun {
|
||||
return {
|
||||
id: "99999999-9999-4999-8999-999999999999",
|
||||
databaseId: "11111111-1111-4111-8111-111111111111",
|
||||
modelId: "local-qwen",
|
||||
scope: "selected_columns",
|
||||
status: "completed",
|
||||
total: 2,
|
||||
suggestedSensitive: 1,
|
||||
suggestedNonSensitive: 1,
|
||||
createdAt: "2026-08-28T11:00:00Z",
|
||||
startedAt: "2026-08-28T11:00:00Z",
|
||||
updatedAt: "2026-08-28T11:00:01Z",
|
||||
finishedAt: "2026-08-28T11:00:01Z",
|
||||
errorSummary: null,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
function registerCompletedSyncRun(run: CatalogSyncRun) {
|
||||
const completed: CatalogSyncRun = {
|
||||
...run,
|
||||
@@ -167,6 +192,116 @@ const synchronizationScopes = [
|
||||
{ scope: "all", label: "Synchronize all" },
|
||||
] as const;
|
||||
|
||||
test("renders Fleet Ledger with real metrics, conceptual row tooltips, and contextual model selection", async () => {
|
||||
const user = userEvent.setup();
|
||||
server.use(
|
||||
http.get("/api/catalog/metrics", () => HttpResponse.json({
|
||||
scope: "global",
|
||||
databaseId: null,
|
||||
tables: 163,
|
||||
columns: 2_275,
|
||||
sensitiveColumns: 5,
|
||||
relationships: 18,
|
||||
descriptionTargets: 2_438,
|
||||
describedTargets: 1_828,
|
||||
descriptionCoverage: 75,
|
||||
updatedAt: "2026-08-31T09:00:00Z",
|
||||
})),
|
||||
http.get("/api/catalog/metadata-generation/models", () => HttpResponse.json({
|
||||
models: [{ id: "local-qwen", label: "Local Qwen" }],
|
||||
default: "local-qwen",
|
||||
})),
|
||||
);
|
||||
renderPage({ rows: [makeDatabase()], presentation: "fleet" });
|
||||
|
||||
const pageHeading = await screen.findByRole("heading", { name: "Database management" });
|
||||
expect(pageHeading).toBeVisible();
|
||||
const applicationBar = pageHeading.closest("header");
|
||||
expect(applicationBar).not.toBeNull();
|
||||
expect(within(applicationBar!).getByRole("region", { name: "Catalog status" })).toBeVisible();
|
||||
expect(within(applicationBar!).getByRole("group", { name: "Database management actions" })).toBeVisible();
|
||||
expect(screen.queryByText("Thoth catalog · Fleet ledger")).not.toBeInTheDocument();
|
||||
expect(screen.queryByText("Inspect physical metadata, curate descriptions and control catalog operations.")).not.toBeInTheDocument();
|
||||
expect(screen.queryByRole("button", { name: "Back to workspace" })).not.toBeInTheDocument();
|
||||
const summary = screen.getByRole("region", { name: "Fleet summary" });
|
||||
expect(await within(summary).findByText("2,275")).toBeVisible();
|
||||
expect(within(summary).getByText("75%")).toBeVisible();
|
||||
expect(screen.queryByRole("combobox", { name: "Metadata description model" })).not.toBeInTheDocument();
|
||||
|
||||
const databaseRow = await screen.findByRole("row", { name: /Policlinico San Donato/ });
|
||||
const tablesAction = within(databaseRow).getByRole("button", { name: "View tables for Policlinico San Donato" });
|
||||
const detailsAction = within(databaseRow).getByRole("button", { name: "View Policlinico San Donato" });
|
||||
expect(tablesAction.parentElement).toHaveAttribute("data-tooltip", "Tables");
|
||||
expect(tablesAction).not.toHaveAttribute("title");
|
||||
expect(detailsAction).toHaveAttribute("data-tooltip", "Details");
|
||||
|
||||
const manager = screen.getByRole("main", { name: "Database management" });
|
||||
await user.click(within(databaseRow).getByRole("button", { name: "Edit Policlinico San Donato" }));
|
||||
const editDrawer = await screen.findByRole("dialog", { name: "Edit database" });
|
||||
expect(manager).toContainElement(editDrawer);
|
||||
expect(editDrawer).toHaveAttribute("aria-modal", "false");
|
||||
await user.click(within(editDrawer).getByRole("button", { name: "Close database panel" }));
|
||||
|
||||
const restoredDatabaseRow = await screen.findByRole("row", { name: /Policlinico San Donato/ });
|
||||
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();
|
||||
await user.selectOptions(actionPicker, "generate-missing");
|
||||
expect(await screen.findByRole("combobox", { name: "Metadata description model" })).toHaveValue("local-qwen");
|
||||
|
||||
});
|
||||
|
||||
test("keeps only the local back action while browsing Fleet Ledger columns", async () => {
|
||||
const user = userEvent.setup();
|
||||
server.use(
|
||||
http.get("/api/catalog/databases/:databaseId/tables", () => HttpResponse.json([patientsTable])),
|
||||
http.get("/api/catalog/databases/:databaseId/tables/:tableId/columns", () => HttpResponse.json([])),
|
||||
);
|
||||
renderPage({ rows: [makeDatabase()], presentation: "fleet" });
|
||||
|
||||
const databaseRow = await screen.findByRole("row", { name: /Policlinico San Donato/ });
|
||||
await user.click(within(databaseRow).getByRole("button", {
|
||||
name: "View tables for Policlinico San Donato",
|
||||
}));
|
||||
expect(await screen.findByRole("button", { name: "Back to databases" })).toBeVisible();
|
||||
|
||||
await user.click(await screen.findByRole("button", { name: "View columns for patients" }));
|
||||
expect(await screen.findByRole("button", { name: "Back to tables" })).toBeVisible();
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByRole("button", { name: "Back to databases" })).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
await user.click(screen.getByRole("button", { name: "Back to tables" }));
|
||||
expect(await screen.findByRole("button", { name: "Back to databases" })).toBeVisible();
|
||||
expect(screen.queryByRole("button", { name: "Back to tables" })).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
test("reopens database synchronization history when no run is active", async () => {
|
||||
const user = userEvent.setup();
|
||||
const completed: CatalogSyncRun = {
|
||||
...makeSyncRun("all"),
|
||||
state: "succeeded",
|
||||
phase: "completed",
|
||||
startedAt: "2026-08-27T10:00:00Z",
|
||||
finishedAt: "2026-08-27T10:00:01Z",
|
||||
};
|
||||
server.use(
|
||||
http.get("/api/catalog/databases/:databaseId/sync-runs", () => HttpResponse.json([completed])),
|
||||
http.get("/api/catalog/sync-runs/:runId", () => HttpResponse.json(completed)),
|
||||
http.get("/api/catalog/sync-runs/:runId/events-list", () => HttpResponse.json([])),
|
||||
);
|
||||
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" }));
|
||||
|
||||
const drawer = await screen.findByRole("dialog", { name: "Schema synchronization" });
|
||||
expect(drawer).toBeVisible();
|
||||
expect(screen.getByRole("main", { name: "Database management" })).toContainElement(drawer);
|
||||
expect(await screen.findByRole("heading", { name: "Succeeded" })).toBeVisible();
|
||||
expect(screen.getByRole("region", { name: "Synchronization history" })).toBeVisible();
|
||||
});
|
||||
|
||||
test("selects the configured metadata-description model by default", async () => {
|
||||
server.use(http.get("/api/catalog/metadata-generation/models", () => HttpResponse.json({
|
||||
models: [
|
||||
@@ -181,6 +316,50 @@ test("selects the configured metadata-description model by default", async () =>
|
||||
await waitFor(() => expect(selector).toHaveValue("local-qwen"));
|
||||
});
|
||||
|
||||
test("keeps both run-history buttons visible beside the metadata-description selector", async () => {
|
||||
const user = userEvent.setup();
|
||||
renderPage();
|
||||
|
||||
const selector = await screen.findByRole("combobox", { name: "Metadata description model" });
|
||||
const toolbar = screen.getByRole("group", { name: "Database management controls" });
|
||||
const metadataControls = screen.getByRole("group", { name: "Metadata description controls" });
|
||||
const actions = screen.getByRole("group", { name: "Database management actions" });
|
||||
const descriptionHistoryButton = within(metadataControls).getByRole("button", {
|
||||
name: "Run descriptions generation history",
|
||||
});
|
||||
const suggestionHistoryButton = within(metadataControls).getByRole("button", {
|
||||
name: "Run sensitive suggestions history",
|
||||
});
|
||||
|
||||
expect(toolbar).toHaveClass("sm:items-end", "sm:justify-between");
|
||||
expect(toolbar.firstElementChild).toBe(metadataControls);
|
||||
expect(metadataControls).toContainElement(selector);
|
||||
expect(descriptionHistoryButton).toBeVisible();
|
||||
expect(descriptionHistoryButton).toBeEnabled();
|
||||
expect(descriptionHistoryButton).toHaveTextContent("Run descriptions generation history");
|
||||
expect(descriptionHistoryButton).toHaveClass("disabled:opacity-70");
|
||||
expect(suggestionHistoryButton).toBeVisible();
|
||||
expect(suggestionHistoryButton).toBeEnabled();
|
||||
expect(suggestionHistoryButton).toHaveTextContent("Run sensitive suggestions history");
|
||||
expect(toolbar.lastElementChild).toBe(actions);
|
||||
expect(actions).toHaveClass("sm:justify-end");
|
||||
expect(actions).toContainElement(screen.getByRole("button", { name: "Refresh" }));
|
||||
expect(actions).toContainElement(screen.getByRole("button", { name: "Add database" }));
|
||||
expect(screen.queryByRole("note", {
|
||||
name: "Metadata generation source data disclosure",
|
||||
})).not.toBeInTheDocument();
|
||||
expect(document.getElementById("metadata-generation-source-data-disclosure")).toBeNull();
|
||||
|
||||
await user.click(descriptionHistoryButton);
|
||||
const descriptionDrawer = await screen.findByRole("dialog", { name: "Description generation" });
|
||||
expect(within(descriptionDrawer).getByText("No description generation runs yet.")).toBeVisible();
|
||||
|
||||
await user.click(suggestionHistoryButton);
|
||||
expect(screen.queryByRole("dialog", { name: "Description generation" })).not.toBeInTheDocument();
|
||||
const suggestionDrawer = await screen.findByRole("dialog", { name: "Sensitive suggestion history" });
|
||||
expect(within(suggestionDrawer).getByText("No sensitive suggestion runs yet.")).toBeVisible();
|
||||
});
|
||||
|
||||
test("changes the metadata-description model in page-local state", async () => {
|
||||
const user = userEvent.setup();
|
||||
server.use(http.get("/api/catalog/metadata-generation/models", () => HttpResponse.json({
|
||||
@@ -271,38 +450,6 @@ test("renders only the public metadata-generation model contract", async () => {
|
||||
expect(document.body).not.toHaveTextContent("raw-private-credential");
|
||||
});
|
||||
|
||||
test("discloses bounded transient source samples in database-wide and selected generation views", async () => {
|
||||
const user = userEvent.setup();
|
||||
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([]),
|
||||
));
|
||||
renderPage();
|
||||
|
||||
const disclosure = await screen.findByRole("note", {
|
||||
name: "Metadata generation source data disclosure",
|
||||
});
|
||||
expect(within(disclosure).getByText(
|
||||
"Description generation may send up to five source rows and up to five representative non-null example values to the selected model provider. Values from columns marked sensitive are replaced with plausible synthetic values before the request.",
|
||||
)).toBeVisible();
|
||||
expect(within(disclosure).getByText(
|
||||
"Values from unmarked columns may be sent unchanged. Samples are transient and are not stored in run logs or catalog metadata.",
|
||||
)).toBeVisible();
|
||||
expect(within(disclosure).getByText(
|
||||
"Sensitive-field suggestions use structural metadata only and remain unsaved until you choose Save sensitive fields.",
|
||||
)).toBeVisible();
|
||||
|
||||
await user.click(await screen.findByRole("button", { name: "View Policlinico San Donato" }));
|
||||
await user.click(screen.getByRole("tab", { name: "Tables" }));
|
||||
|
||||
expect(screen.getByRole("note", {
|
||||
name: "Metadata generation source data disclosure",
|
||||
})).toBeVisible();
|
||||
});
|
||||
|
||||
test("starts with a full-width list and applies the row action matrix", async () => {
|
||||
renderPage();
|
||||
|
||||
@@ -377,7 +524,7 @@ test("starts Generate Missing for one configured database without confirmation",
|
||||
scope: "missing",
|
||||
}));
|
||||
expect(screen.queryByText(/replace existing generated descriptions/i)).not.toBeInTheDocument();
|
||||
expect(await screen.findByRole("complementary", { name: "Description generation" })).toBeVisible();
|
||||
expect(await screen.findByRole("dialog", { name: "Description generation" })).toBeVisible();
|
||||
});
|
||||
|
||||
test("confirms Generate All replacement, supports cancel, and sends the database-wide scope", async () => {
|
||||
@@ -419,7 +566,7 @@ test("confirms Generate All replacement, supports cancel, and sends the database
|
||||
modelId: "local-qwen",
|
||||
scope: "all",
|
||||
}));
|
||||
expect(await screen.findByRole("complementary", { name: "Description generation" })).toBeVisible();
|
||||
expect(await screen.findByRole("dialog", { name: "Description generation" })).toBeVisible();
|
||||
});
|
||||
|
||||
test("keeps the database selected and safely explains when no descriptions are eligible", async () => {
|
||||
@@ -445,7 +592,7 @@ test("keeps the database selected and safely explains when no descriptions are e
|
||||
"No eligible catalog tables or columns need description generation.",
|
||||
)).toBeVisible();
|
||||
expect(screen.getByText("1 selected")).toBeVisible();
|
||||
expect(screen.queryByRole("complementary", { name: "Description generation" })).not.toBeInTheDocument();
|
||||
expect(screen.queryByRole("dialog", { name: "Description generation" })).not.toBeInTheDocument();
|
||||
expect(document.body).not.toHaveTextContent("private provider and catalog detail");
|
||||
});
|
||||
|
||||
@@ -605,7 +752,7 @@ test("disables database-wide generation while a description generation is active
|
||||
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: "Generate Missing" }));
|
||||
expect(await screen.findByRole("complementary", { name: "Description generation" })).toBeVisible();
|
||||
expect(await screen.findByRole("dialog", { name: "Description generation" })).toBeVisible();
|
||||
|
||||
databaseRow = await screen.findByRole("row", { name: /Policlinico San Donato/ });
|
||||
await user.click(within(databaseRow).getByRole("checkbox", { name: /toggle row selection/i }));
|
||||
@@ -1177,7 +1324,7 @@ test("starts description generation for multiple selected tables", async () => {
|
||||
scope: "selected_tables",
|
||||
targetIds: [patientsTable.id, visitsTable.id],
|
||||
}));
|
||||
expect(await screen.findByRole("complementary", {
|
||||
expect(await screen.findByRole("dialog", {
|
||||
name: "Description generation",
|
||||
})).toBeVisible();
|
||||
expect(await screen.findByText("Description generation started for 2 tables")).toBeVisible();
|
||||
@@ -1216,18 +1363,42 @@ test("observes an active run from another browser and reopens a terminal run fro
|
||||
renderPage();
|
||||
|
||||
const observe = await screen.findByRole("button", {
|
||||
name: "Observe active description generation",
|
||||
name: "Run descriptions generation history",
|
||||
});
|
||||
expect(observe).toHaveTextContent("Run descriptions generation history");
|
||||
expect(requestedLimit).toBe("50");
|
||||
await user.click(observe);
|
||||
|
||||
const drawer = await screen.findByRole("complementary", { name: "Description generation" });
|
||||
const drawer = await screen.findByRole("dialog", { name: "Description generation" });
|
||||
expect(within(drawer).getByRole("heading", { name: "Running" })).toBeVisible();
|
||||
const history = within(drawer).getByRole("region", { name: "Description generation history" });
|
||||
await user.click(within(history).getByRole("button", { name: /Completed.*missing/i }));
|
||||
expect(await within(drawer).findByRole("heading", { name: "Completed" })).toBeVisible();
|
||||
});
|
||||
|
||||
test("keeps the sensitive suggestion history label stable while showing active status separately", async () => {
|
||||
const runningRun = makeSensitiveDataSuggestionRun({
|
||||
status: "running",
|
||||
finishedAt: null,
|
||||
updatedAt: new Date().toISOString(),
|
||||
});
|
||||
server.use(http.get(
|
||||
"/api/catalog/sensitive-data-suggestion-runs",
|
||||
() => HttpResponse.json([runningRun]),
|
||||
));
|
||||
renderPage();
|
||||
|
||||
const historyButton = await screen.findByRole("button", {
|
||||
name: "Run sensitive suggestions history",
|
||||
});
|
||||
expect(historyButton).toHaveTextContent("Run sensitive suggestions history");
|
||||
await waitFor(() => expect(historyButton).toHaveAttribute(
|
||||
"title",
|
||||
"Sensitive suggestion generation is active",
|
||||
));
|
||||
expect(historyButton.querySelector("[aria-hidden='true'].bg-primary")).not.toBeNull();
|
||||
});
|
||||
|
||||
test.each([
|
||||
"completed",
|
||||
"completed_with_errors",
|
||||
@@ -1256,7 +1427,7 @@ test.each([
|
||||
client.setQueryData(unrelatedColumnKey, []);
|
||||
|
||||
await user.click(await screen.findByRole("button", {
|
||||
name: "View description generation history",
|
||||
name: "Run descriptions generation history",
|
||||
}));
|
||||
|
||||
await waitFor(() => expect(client.getQueryState(tableKey)?.isInvalidated).toBe(true));
|
||||
@@ -1299,7 +1470,7 @@ test("keeps selected tables when description generation cannot start", async ()
|
||||
|
||||
expect(await screen.findByText("A description generation run is already active.")).toBeVisible();
|
||||
expect(screen.getByText("2 selected")).toBeVisible();
|
||||
expect(screen.queryByRole("complementary", { name: "Description generation" })).not.toBeInTheDocument();
|
||||
expect(screen.queryByRole("dialog", { name: "Description generation" })).not.toBeInTheDocument();
|
||||
expect(document.body).not.toHaveTextContent("private provider detail");
|
||||
});
|
||||
|
||||
@@ -1565,7 +1736,7 @@ test("starts one selected column with the configured default model", async () =>
|
||||
scope: "selected_columns",
|
||||
targetIds: [patientIdColumn.id],
|
||||
}));
|
||||
const drawer = await screen.findByRole("complementary", { name: "Description generation" });
|
||||
const drawer = await screen.findByRole("dialog", { name: "Description generation" });
|
||||
expect(drawer).toBeVisible();
|
||||
expect(within(drawer).getByRole("heading", { name: "Queued" })).toBeVisible();
|
||||
expect(await screen.findByText("Description generation started for 1 column")).toBeVisible();
|
||||
@@ -1606,14 +1777,23 @@ test("shows database sensitive suggestions only for a selection and rejects mult
|
||||
test("requests database-level sensitive suggestions for the only selected database", async () => {
|
||||
const user = userEvent.setup();
|
||||
let suggestionBody: unknown;
|
||||
let suggestionFinished = false;
|
||||
let historyCalls = 0;
|
||||
const run = makeSensitiveDataSuggestionRun({ scope: "all", total: 1 });
|
||||
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/sensitive-data-suggestion-runs", () => {
|
||||
historyCalls += 1;
|
||||
return HttpResponse.json(suggestionFinished ? [run] : []);
|
||||
}),
|
||||
http.post("/api/catalog/databases/:databaseId/sensitive-data-suggestions", async ({ request }) => {
|
||||
suggestionBody = await request.json();
|
||||
suggestionFinished = true;
|
||||
return HttpResponse.json({
|
||||
run,
|
||||
suggestions: [{
|
||||
columnId: patientIdColumn.id,
|
||||
tableId: patientsTable.id,
|
||||
@@ -1633,7 +1813,37 @@ test("requests database-level sensitive suggestions for the only selected databa
|
||||
await user.click(screen.getByRole("button", { name: "Suggest sensitive fields" }));
|
||||
|
||||
await waitFor(() => expect(suggestionBody).toEqual({ modelId: "local-qwen", scope: "all" }));
|
||||
expect(await screen.findByRole("complementary", { name: "Sensitive field review" })).toBeVisible();
|
||||
expect(await screen.findByRole("dialog", { name: "Sensitive field review" })).toBeVisible();
|
||||
await waitFor(() => expect(historyCalls).toBeGreaterThanOrEqual(2));
|
||||
});
|
||||
|
||||
test("refetches sensitive suggestion history after a failed request", async () => {
|
||||
const user = userEvent.setup();
|
||||
let historyCalls = 0;
|
||||
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/sensitive-data-suggestion-runs", () => {
|
||||
historyCalls += 1;
|
||||
return HttpResponse.json([]);
|
||||
}),
|
||||
http.post("/api/catalog/databases/:databaseId/sensitive-data-suggestions", () => (
|
||||
HttpResponse.json({
|
||||
code: "sensitive_data_suggestion_invalid_response",
|
||||
message: "The LLM returned an incomplete or invalid classification. No suggestions were applied.",
|
||||
}, { status: 502 })
|
||||
)),
|
||||
);
|
||||
renderPage({ rows: [makeDatabase()] });
|
||||
|
||||
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: "Suggest sensitive fields" }));
|
||||
|
||||
await waitFor(() => expect(historyCalls).toBeGreaterThanOrEqual(2));
|
||||
expect(screen.queryByRole("dialog", { name: "Sensitive field review" })).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
test("requests sensitive suggestions only for selected tables", async () => {
|
||||
@@ -1687,7 +1897,7 @@ test("requests sensitive suggestions only for selected tables", async () => {
|
||||
scope: "selected_tables",
|
||||
targetIds: [visitsTable.id],
|
||||
}));
|
||||
expect(await screen.findByRole("complementary", { name: "Sensitive field review" })).toBeVisible();
|
||||
expect(await screen.findByRole("dialog", { name: "Sensitive field review" })).toBeVisible();
|
||||
});
|
||||
|
||||
test("reviews AI-sensitive-field suggestions as an editable draft and saves only changed columns", async () => {
|
||||
@@ -1798,7 +2008,7 @@ test("reviews AI-sensitive-field suggestions as an editable draft and saves only
|
||||
scope: "selected_columns",
|
||||
targetIds: [idColumn.id, nameColumn.id],
|
||||
}));
|
||||
const review = await screen.findByRole("complementary", { name: "Sensitive field review" });
|
||||
const review = await screen.findByRole("dialog", { name: "Sensitive field review" });
|
||||
expect(within(review).getByRole("checkbox", { name: "Protect patients.id" })).toBeChecked();
|
||||
expect(within(review).getByRole("checkbox", { name: "Protect patients.name" })).toBeChecked();
|
||||
expect(patches).toHaveLength(0);
|
||||
@@ -1814,7 +2024,7 @@ test("reviews AI-sensitive-field suggestions as an editable draft and saves only
|
||||
},
|
||||
}]));
|
||||
expect(await screen.findByText("Saved 1 sensitive flag")).toBeVisible();
|
||||
await waitFor(() => expect(screen.queryByRole("complementary", { name: "Sensitive field review" })).not.toBeInTheDocument());
|
||||
await waitFor(() => expect(screen.queryByRole("dialog", { name: "Sensitive field review" })).not.toBeInTheDocument());
|
||||
await waitFor(() => expect(screen.getByRole("checkbox", { name: "Sensitive data for id" })).toBeChecked());
|
||||
expect(screen.getByRole("checkbox", { name: "Sensitive data for name" })).not.toBeChecked();
|
||||
});
|
||||
@@ -1875,7 +2085,7 @@ test("polls a description run and renders its events in sequence order", async (
|
||||
await user.click(screen.getByRole("button", { name: "Actions" }));
|
||||
await user.click(await screen.findByRole("menuitem", { name: "Generate description" }));
|
||||
|
||||
const drawer = await screen.findByRole("complementary", { name: "Description generation" });
|
||||
const drawer = await screen.findByRole("dialog", { name: "Description generation" });
|
||||
expect(within(drawer).getByText(/local-qwen/)).toBeVisible();
|
||||
expect(within(drawer).getByText(/selected columns/)).toBeVisible();
|
||||
expect(await within(drawer).findByRole("heading", { name: "Completed" }, { timeout: 4_000 })).toBeVisible();
|
||||
@@ -1992,7 +2202,7 @@ test("keeps the selected column and shows a safe message when generation cannot
|
||||
|
||||
expect(await screen.findByText("A description generation run is already active.")).toBeVisible();
|
||||
expect(screen.getByText("1 selected")).toBeVisible();
|
||||
expect(screen.queryByRole("complementary", { name: "Description generation" })).not.toBeInTheDocument();
|
||||
expect(screen.queryByRole("dialog", { name: "Description generation" })).not.toBeInTheDocument();
|
||||
expect(document.body).not.toHaveTextContent(/private-provider|sk-private-key/);
|
||||
});
|
||||
|
||||
@@ -2057,7 +2267,7 @@ test("shows a basic failed run without exposing private model or provider fields
|
||||
await user.click(screen.getByRole("button", { name: "Actions" }));
|
||||
await user.click(await screen.findByRole("menuitem", { name: "Generate description" }));
|
||||
|
||||
const drawer = await screen.findByRole("complementary", { name: "Description generation" });
|
||||
const drawer = await screen.findByRole("dialog", { name: "Description generation" });
|
||||
expect(await within(drawer).findByRole("heading", { name: "Failed" })).toBeVisible();
|
||||
expect(within(drawer).getByText(/Approved model/)).toBeVisible();
|
||||
expect(within(drawer).getByText("The model request failed before a description was saved.")).toBeVisible();
|
||||
@@ -2200,7 +2410,7 @@ test("disables another generation start while the selected-column run is active"
|
||||
await user.click(within(columnRow!).getByRole("checkbox", { name: /toggle row selection/i }));
|
||||
await user.click(screen.getByRole("button", { name: "Actions" }));
|
||||
await user.click(await screen.findByRole("menuitem", { name: "Generate description" }));
|
||||
expect(await screen.findByRole("complementary", { name: "Description generation" })).toBeVisible();
|
||||
expect(await screen.findByRole("dialog", { name: "Description generation" })).toBeVisible();
|
||||
|
||||
await user.click(screen.getByRole("button", { name: "Actions" }));
|
||||
expect(await screen.findByRole("menuitem", { name: "Generate description" }))
|
||||
@@ -2495,10 +2705,16 @@ test("opens the durable job drawer and confirms its exact destructive plan", asy
|
||||
await user.click(screen.getByRole("tab", { name: "Tables" }));
|
||||
await user.click(await screen.findByRole("button", { name: "Sync tables" }));
|
||||
|
||||
const synchronizationDrawer = await screen.findByRole("complementary", {
|
||||
const synchronizationDrawer = await screen.findByRole("dialog", {
|
||||
name: "Schema synchronization",
|
||||
});
|
||||
expect(synchronizationDrawer).toHaveClass("inset-y-2", "sm:inset-y-4");
|
||||
expect(synchronizationDrawer).toHaveAttribute("data-catalog-panel");
|
||||
expect(synchronizationDrawer.querySelector(
|
||||
':scope > [data-catalog-panel-region="header"]',
|
||||
)).toBeInTheDocument();
|
||||
expect(synchronizationDrawer.querySelector(
|
||||
':scope > [data-catalog-panel-region="body"]',
|
||||
)).toBeInTheDocument();
|
||||
expect(within(synchronizationDrawer).getByRole("listitem", {
|
||||
name: "Confirmation required, in progress",
|
||||
})).toHaveAttribute("aria-current", "step");
|
||||
|
||||
Reference in New Issue
Block a user