feat(xugu): expose tablespaces and datafiles in tree

This commit is contained in:
Elias authored and GitHub committed 2026-09-01 00:34:12 +08:00
1 parent afe3b3f3c2
commit 351a8b699b
32 files changed
+811 -4

No files matched your search

@@ -685,6 +685,8 @@ const groupTypes: Set<TreeNodeType> = new Set([
"group-types",
"group-partitions",
"group-extensions",
"group-tablespaces",
"group-datafiles",
]);
function isGroupLabel(node: TreeNode): boolean {
@@ -807,6 +809,35 @@ async function toggle(requestId = beginNavigationRequest()) {
return;
}
// Xugu tablespace rows and their datafile groups are eagerly materialized
// with the tablespace listing; expanding them is a pure local toggle.
if (node.type === "tablespace" || node.type === "group-datafiles") {
node.isExpanded = !node.isExpanded;
emitNodeToggled(node, wasExpanded);
return;
}
if (node.type === "group-tablespaces") {
if (connectionStore.canUseLoadedTreeNodeToggle(node)) {
node.isExpanded = !node.isExpanded;
if (wasExpanded && shouldReleaseCollapsedTreeNodeChildren()) connectionStore.releaseCollapsedTreeNodeChildren(node.id);
emitNodeToggled(node, wasExpanded);
return;
}
try {
await connectionStore.loadXuguTablespaces(node, { preserveCollapsedChildren: !shouldReleaseCollapsedTreeNodeChildren() });
emitNodeToggled(node, wasExpanded);
} catch (e: any) {
if (!isCurrentNavigationRequest(requestId)) return;
if (!wasExpanded) node.isExpanded = false;
const errMsg = e?.message || String(e);
if (errMsg.includes(CONNECTION_ATTEMPT_CANCELLED_MESSAGE)) return;
toast(t("connection.connectFailed", { message: translateBackendError(t, e) }), 5000);
openDriverStoreForInstallError(errMsg);
}
return;
}
if (isXuguTypeMemberContainer(node, connectionStore.getConfig(node.connectionId || "")?.db_type)) {
await connectionStore.loadXuguTypeMembers(node, { preserveCollapsedChildren: !shouldReleaseCollapsedTreeNodeChildren() });
emitNodeToggled(node, wasExpanded);
@@ -240,6 +240,10 @@ function getIconInfo(node: TreeNode): { icon: any; colorClass: string } | null {
return { icon: node.isExpanded ? FolderOpen : FolderClosed, colorClass: "text-amber-500" };
case "database":
return { icon: Database, colorClass: "text-yellow-500" };
case "tablespace":
return { icon: Database, colorClass: "text-orange-500" };
case "datafile":
return { icon: FileCode, colorClass: "text-slate-500" };
case "linked-server-root":
return { icon: Network, colorClass: "text-blue-500" };
case "linked-server":
@@ -395,6 +399,10 @@ function getIconInfo(node: TreeNode): { icon: any; colorClass: string } | null {
return { icon: node.isExpanded ? FolderOpen : FolderClosed, colorClass: "text-green-400" };
case "group-extensions":
return { icon: Package, colorClass: "text-violet-500" };
case "group-tablespaces":
return { icon: Database, colorClass: "text-orange-500" };
case "group-datafiles":
return { icon: node.isExpanded ? FolderOpen : FolderClosed, colorClass: "text-slate-500" };
case "extension":
return { icon: Package, colorClass: "text-violet-400" };
case "load-more":
@@ -432,6 +440,7 @@ function displayLabel(node: TreeNode): string {
function treeNodeSecondaryValue(node: TreeNode): string | undefined {
if (node.type === "type" && node.customTypeKind) return t(`customType.kinds.${node.customTypeKind}`);
if (node.type === "type-member") return (node.meta as CustomTypeTreeMemberMeta | undefined)?.displayValue;
if (node.type === "datafile") return node.xuguDatafilePath;
return undefined;
}
@@ -21,6 +21,9 @@ const connectionStore = {
loadObjectGroupChildren: vi.fn(async (node: TreeNode) => {
node.isExpanded = true;
}),
loadXuguTablespaces: vi.fn(async (node: TreeNode) => {
node.isExpanded = true;
}),
};
const queryStore = {
@@ -421,4 +424,62 @@ describe("SidebarTreeRuntimeHost expansion", () => {
expect(packageFunctionGroup.isExpanded).toBe(true);
expect(packageFunctionGroup.children).toHaveLength(401);
});
it("toggles Xugu tablespace and datafile group nodes locally", async () => {
const datafileGroup: TreeNode = {
id: "xugu:SHOP_DEMO:tablespace:SYSTEM:datafiles",
label: "tree.datafiles",
type: "group-datafiles",
parentType: "tablespace",
parentName: "SYSTEM",
connectionId: "xugu",
database: "SHOP_DEMO",
objectCount: 2,
isExpanded: false,
children: [
{ id: "xugu:SHOP_DEMO:tablespace:SYSTEM:datafile:1", label: "system_01.dbf", type: "datafile", parentType: "tablespace", parentName: "SYSTEM", connectionId: "xugu", database: "SHOP_DEMO", objectName: "system_01.dbf" },
{ id: "xugu:SHOP_DEMO:tablespace:SYSTEM:datafile:2", label: "system_02.dbf", type: "datafile", parentType: "tablespace", parentName: "SYSTEM", connectionId: "xugu", database: "SHOP_DEMO", objectName: "system_02.dbf" },
],
};
const tablespaceNode: TreeNode = {
id: "xugu:SHOP_DEMO:tablespace:SYSTEM",
label: "SYSTEM",
type: "tablespace",
connectionId: "xugu",
database: "SHOP_DEMO",
objectName: "SYSTEM",
isExpanded: false,
children: [datafileGroup],
};
connectionStore.treeNodes = [tablespaceNode];
connectionStore.getConfig.mockReturnValue({ db_type: "xugu" });
connectionStore.canUseLoadedTreeNodeToggle.mockReturnValue(false);
const host = ref<InstanceType<typeof SidebarTreeRuntimeHost> | null>(null);
const toggled = vi.fn();
const app = createApp(
defineComponent({
setup: () => () => h(SidebarTreeRuntimeHost, { ref: host, node: tablespaceNode, depth: 0, onNodeToggled: toggled }),
}),
);
mountedApps.push(app);
const container = document.createElement("div");
document.body.append(container);
app.use(i18n);
app.mount(container);
host.value?.toggleNode(tablespaceNode);
await nextTick();
expect(tablespaceNode.isExpanded).toBe(true);
expect(connectionStore.loadXuguTablespaces).not.toHaveBeenCalled();
expect(toggled).toHaveBeenCalledWith(tablespaceNode, true);
host.value?.toggleNode(datafileGroup);
await nextTick();
expect(datafileGroup.isExpanded).toBe(true);
expect(connectionStore.loadXuguTablespaces).not.toHaveBeenCalled();
expect(toggled).toHaveBeenCalledWith(datafileGroup, true);
});
});