feat: consolidate database management work

Add catalog-owned logical relationships and runtime snapshots, extend the database-management UI and validation coverage, and document the updated operational workflow.

Keep active sensitive-generation status in a tooltip and indicator, and update the layout E2E to follow the history action in its new database-scoped location.
This commit is contained in:
Codex
2026-09-01 14:46:55 +02:00
parent f586152636
commit 076c9742c5
73 changed files with 6966 additions and 610 deletions
+105 -1
View File
@@ -1,7 +1,15 @@
import { http, HttpResponse } from "msw";
import { expect, test } from "vitest";
import { server } from "../test/msw";
import { getCatalogMetrics, type CatalogMetrics } from "./catalog-databases";
import {
createCatalogRelationship,
deleteCatalogRelationship,
getCatalogMetrics,
rebuildGeneratedRelationships,
setCatalogRelationshipStatus,
type CatalogMetrics,
type CatalogRelationship,
} from "./catalog-databases";
const databaseId = "d4baf0f5-b9c3-4dc5-aad2-2f5676a36e64";
@@ -58,3 +66,99 @@ test("propagates an unsuccessful catalog metrics response", async () => {
await expect(getCatalogMetrics()).rejects.toMatchObject({ status: 503 });
});
const logicalRelationship: CatalogRelationship = {
id: "11111111-1111-4111-8111-111111111111",
databaseId,
constraintName: null,
sourceTableId: "22222222-2222-4222-8222-222222222222",
sourceTableName: "orders",
targetTableId: "33333333-3333-4333-8333-333333333333",
targetTableName: "users",
updateRule: null,
deleteRule: null,
deferrable: false,
initiallyDeferred: false,
columns: [{
position: 1,
sourceColumnId: "44444444-4444-4444-8444-444444444444",
sourceColumnName: "user_id",
targetColumnId: "55555555-5555-4555-8555-555555555555",
targetColumnName: "id",
}],
origin: "manual",
status: "active",
lastSyncedDatabaseVersion: null,
lastSyncedAt: null,
createdAt: "2026-08-31T12:00:00.000Z",
updatedAt: "2026-08-31T12:00:00.000Z",
};
test("creates a manual relationship from the selected source and target columns", async () => {
let requestBody: unknown;
server.use(http.post("/api/catalog/databases/:databaseId/relationships", async ({ request }) => {
requestBody = await request.json();
return HttpResponse.json(logicalRelationship, { status: 201 });
}));
await expect(createCatalogRelationship(
databaseId,
logicalRelationship.columns[0].sourceColumnId,
logicalRelationship.columns[0].targetColumnId,
)).resolves.toEqual(logicalRelationship);
expect(requestBody).toEqual({
sourceColumnId: logicalRelationship.columns[0].sourceColumnId,
targetColumnId: logicalRelationship.columns[0].targetColumnId,
});
});
test("rebuilds generated relationships for one database", async () => {
let calls = 0;
server.use(http.post(
"/api/catalog/databases/:databaseId/relationships/rebuild-generated",
() => {
calls += 1;
return HttpResponse.json({ added: 12, alreadyPresent: 7, excluded: 3, ambiguous: 2 });
},
));
await expect(rebuildGeneratedRelationships(databaseId)).resolves.toEqual({
added: 12,
alreadyPresent: 7,
excluded: 3,
ambiguous: 2,
});
expect(calls).toBe(1);
});
test("changes whether a logical relationship is active or excluded", async () => {
let requestBody: unknown;
server.use(http.patch(
"/api/catalog/databases/:databaseId/relationships/:relationshipId",
async ({ request }) => {
requestBody = await request.json();
return HttpResponse.json({ ...logicalRelationship, status: "excluded" });
},
));
await expect(setCatalogRelationshipStatus(
databaseId,
logicalRelationship.id,
"excluded",
)).resolves.toMatchObject({ status: "excluded" });
expect(requestBody).toEqual({ status: "excluded" });
});
test("permanently deletes a logical relationship", async () => {
let calls = 0;
server.use(http.delete(
"/api/catalog/databases/:databaseId/relationships/:relationshipId",
() => {
calls += 1;
return new HttpResponse(null, { status: 204 });
},
));
await expect(deleteCatalogRelationship(databaseId, logicalRelationship.id)).resolves.toBeUndefined();
expect(calls).toBe(1);
});