feat(sidebar): show table metadata group counts

This commit is contained in:
cowking authored and GitHub committed 2026-09-11 14:14:30 +08:00
1 parent c834421c49
commit 72faf4887b
4 files changed
+191 -11

No files matched your search

@@ -149,6 +149,68 @@ describe("connectionStore Xugu table child metadata", () => {
expect(subpartitions.isExpanded).toBe(true);
});
it("records exact counts for every successfully loaded table metadata group", async () => {
const getColumns = vi.fn().mockResolvedValue([
{ name: "ORDER_ID", data_type: "INT", is_nullable: false, column_default: null, is_primary_key: true, extra: null },
{ name: "CUSTOMER_ID", data_type: "INT", is_nullable: false, column_default: null, is_primary_key: false, extra: null },
]);
const listIndexes = vi.fn().mockResolvedValue([{ name: "IDX_CUSTOMER", columns: ["CUSTOMER_ID"], unique: false }]);
const listForeignKeys = vi.fn().mockResolvedValue([{ name: "FK_CUSTOMER", column: "CUSTOMER_ID", ref_table: "CUSTOMERS", ref_column: "ID" }]);
const listTriggers = vi.fn().mockResolvedValue([{ name: "TR_AUDIT", timing: "BEFORE", event: "INSERT" }]);
const listConstraints = vi.fn().mockResolvedValue([{ name: "PK_SHOP_ORDERS", constraint_type: "PRIMARY KEY", valid: true }]);
const listPartitions = vi.fn().mockResolvedValue([]);
const listSubpartitions = vi.fn().mockResolvedValue([]);
const { store, tableId } = await setup("xugu", {
getColumns,
listIndexes,
listForeignKeys,
listTriggers,
listConstraints,
listPartitions,
listSubpartitions,
});
expect(getColumns).not.toHaveBeenCalled();
expect(listIndexes).not.toHaveBeenCalled();
expect(listForeignKeys).not.toHaveBeenCalled();
expect(listTriggers).not.toHaveBeenCalled();
expect(listConstraints).not.toHaveBeenCalled();
expect(listPartitions).not.toHaveBeenCalled();
expect(listSubpartitions).not.toHaveBeenCalled();
const groupTypes = ["group-columns", "group-indexes", "group-fkeys", "group-triggers", "group-constraints", "group-table-partitions", "group-table-subpartitions"] as const;
for (const groupType of groupTypes) {
const group = findNode(store.treeNodes, tableId)?.children?.find((node) => node.type === groupType);
if (!group) throw new Error(`Missing ${groupType}`);
await store.loadTreeNodeChildren(group);
}
expect(findNode(store.treeNodes, tableId)?.children?.map((node) => [node.type, node.objectCount])).toEqual([
["group-columns", 2],
["group-constraints", 1],
["group-fkeys", 1],
["group-triggers", 1],
["group-indexes", 1],
["group-table-partitions", 0],
["group-table-subpartitions", 0],
]);
});
it("counts the normalized foreign key nodes for a composite foreign key", async () => {
const listForeignKeys = vi.fn().mockResolvedValue([
{ name: "FK_ORDER_CUSTOMER", column: "CUSTOMER_TENANT_ID", ref_table: "CUSTOMERS", ref_column: "TENANT_ID" },
{ name: "FK_ORDER_CUSTOMER", column: "CUSTOMER_ID", ref_table: "CUSTOMERS", ref_column: "ID" },
]);
const { config, store, tableId } = await setup("mysql", { listForeignKeys });
const foreignKeys = findNode(store.treeNodes, `${tableId}:__fkeys`)!;
await store.loadTreeNodeChildren(foreignKeys);
expect(listForeignKeys).toHaveBeenCalledWith(config.id, "SHOP_DEMO", "SYSDBA", "SHOP_ORDERS", undefined);
expect(foreignKeys.children).toHaveLength(1);
expect(foreignKeys.objectCount).toBe(1);
});
it("keeps a rejected Xugu metadata group collapsed and clears its loading state", async () => {
const listPartitions = vi.fn().mockRejectedValue(new Error("metadata denied"));
const { store, tableId } = await setup("xugu", { listPartitions });
@@ -159,6 +221,7 @@ describe("connectionStore Xugu table child metadata", () => {
expect(partitions.isLoading).toBe(false);
expect(partitions.isExpanded).toBe(false);
expect(partitions.children).toEqual([]);
expect(partitions.objectCount).toBeUndefined();
});
it("shows Xugu trigger level and status without changing other database labels", async () => {
+16 -11
View File
@@ -1683,6 +1683,11 @@ export const useConnectionStore = defineStore("connection", () => {
syncConfirmedEmptyTreeNodeId(parent);
}
function setLoadedTableMetadataChildren(parent: TreeNode, children: TreeNode[]) {
setChildren(parent, children);
parent.objectCount = parent.children!.length;
}
function removePinnedTreeNodes(nodes: readonly TreeNode[], canonicalize: PinnedTreeNodeIdentityCanonicalizer = (identity) => identity, legacyKeys: readonly string[] = []): boolean {
const nextPinnedOrder = removePinnedTreeNodesFromOrder(pinnedTreeNodeOrder.value, nodes, canonicalize, legacyKeys);
if (nextPinnedOrder.length === pinnedTreeNodeOrder.value.length && nextPinnedOrder.every((key, index) => key === pinnedTreeNodeOrder.value[index])) return false;
@@ -6405,7 +6410,7 @@ export const useConnectionStore = defineStore("connection", () => {
const fields = await listMongoCompletionFields(connectionId, database, table);
const targetNode = treeNodeLoadTarget(load);
if (!targetNode) return;
setChildren(
setLoadedTableMetadataChildren(
targetNode,
fields.map((field) => {
const column = {
@@ -6436,7 +6441,7 @@ export const useConnectionStore = defineStore("connection", () => {
if (!targetNode) return;
const connConfig = getConfig(connectionId);
const isGaussdbM = effectiveDatabaseTypeForConnection(connConfig) === "gaussdb" && connConfig?.driver_profile?.toLowerCase() === "gaussdb-m";
setChildren(
setLoadedTableMetadataChildren(
targetNode,
columns.map((col) => ({
id: `${parentId}:${col.name}`,
@@ -6471,7 +6476,7 @@ export const useConnectionStore = defineStore("connection", () => {
if (!metadataCapabilities.indexes || isMongoView) {
const targetNode = treeNodeLoadTarget(load);
if (!targetNode) return;
setChildren(targetNode, []);
setLoadedTableMetadataChildren(targetNode, []);
targetNode.isExpanded = true;
return;
}
@@ -6480,7 +6485,7 @@ export const useConnectionStore = defineStore("connection", () => {
const targetNode = treeNodeLoadTarget(load);
if (!targetNode) return;
const mongoCollectionKind = effectiveDbType === "mongodb" && targetNode.type === "group-indexes" ? mongoCollectionKindFromNode(targetNode) : undefined;
setChildren(
setLoadedTableMetadataChildren(
targetNode,
indexes.map((idx) => ({
id: `${parentId}:${idx.name}`,
@@ -6513,7 +6518,7 @@ export const useConnectionStore = defineStore("connection", () => {
if (!metadataCapabilities.foreignKeys) {
const targetNode = treeNodeLoadTarget(load);
if (!targetNode) return;
setChildren(targetNode, []);
setLoadedTableMetadataChildren(targetNode, []);
targetNode.isExpanded = true;
return;
}
@@ -6525,7 +6530,7 @@ export const useConnectionStore = defineStore("connection", () => {
indexCompletionForeignKeys(connectionId, database, table, schema, sqlCompletionForeignKeys(fkeys));
const targetNode = treeNodeLoadTarget(load);
if (!targetNode) return;
setChildren(
setLoadedTableMetadataChildren(
targetNode,
fkeys.map((fk) => ({
id: `${parentId}:${fk.name}`,
@@ -6558,7 +6563,7 @@ export const useConnectionStore = defineStore("connection", () => {
if (!metadataCapabilities.triggers) {
const targetNode = treeNodeLoadTarget(load);
if (!targetNode) return;
setChildren(targetNode, []);
setLoadedTableMetadataChildren(targetNode, []);
targetNode.isExpanded = true;
return;
}
@@ -6567,7 +6572,7 @@ export const useConnectionStore = defineStore("connection", () => {
const targetNode = treeNodeLoadTarget(load);
if (!targetNode) return;
const isXugu = effectiveDatabaseTypeForConnection(getConfig(connectionId)) === "xugu";
setChildren(
setLoadedTableMetadataChildren(
targetNode,
triggers.map((tr) => {
const xuguDetails = isXugu ? [tr.timing, tr.event, tr.level, tr.enabled === false ? i18n.global.t("objects.disabled") : null, tr.valid === false ? i18n.global.t("objects.invalid") : null].filter(Boolean).join(" · ") : `${tr.timing} ${tr.event}`;
@@ -6604,7 +6609,7 @@ export const useConnectionStore = defineStore("connection", () => {
const constraints = await api.listConstraints(connectionId, database, metadataQuerySchema(connectionId, database, schema), table, catalog);
const targetNode = treeNodeLoadTarget(load);
if (!targetNode) return;
setChildren(
setLoadedTableMetadataChildren(
targetNode,
constraints.map((constraint) => ({
id: `${parentId}:${constraint.name}`,
@@ -6635,7 +6640,7 @@ export const useConnectionStore = defineStore("connection", () => {
const partitions = await api.listPartitions(connectionId, database, metadataQuerySchema(connectionId, database, schema), table, catalog);
const targetNode = treeNodeLoadTarget(load);
if (!targetNode) return;
setChildren(
setLoadedTableMetadataChildren(
targetNode,
partitions.map((partition) => ({
id: `${parentId}:${partition.name}`,
@@ -6666,7 +6671,7 @@ export const useConnectionStore = defineStore("connection", () => {
const partitions = await api.listSubpartitions(connectionId, database, metadataQuerySchema(connectionId, database, schema), table, catalog);
const targetNode = treeNodeLoadTarget(load);
if (!targetNode) return;
setChildren(
setLoadedTableMetadataChildren(
targetNode,
partitions.map((partition) => ({
id: `${parentId}:${partition.name}`,