fix(sidebar): search databases across connections
This commit is contained in:
parent
cfd5f31473
commit
9dbd4902e5
|
|
@ -163,6 +163,12 @@ function isSimpleObjectSearchParent(node: TreeNode): boolean {
|
|||
}
|
||||
|
||||
function collectExpandedObjectSearchTargets(node: TreeNode, tasks: Promise<void>[], refreshedNodeIds?: Set<string>) {
|
||||
if (refreshedNodeIds && node.type === "connection" && node.connectionId) {
|
||||
if (store.connectedIds.has(node.connectionId)) {
|
||||
tasks.push(store.loadConnectedConnectionRootForSidebarSearch(node.connectionId));
|
||||
}
|
||||
if (node.connectionId !== store.activeConnectionId) return;
|
||||
}
|
||||
if (refreshedNodeIds && isSimpleObjectSearchParent(node)) {
|
||||
refreshedNodeIds.add(node.id);
|
||||
tasks.push(store.refreshTreeNode(node));
|
||||
|
|
|
|||
|
|
@ -479,14 +479,14 @@ async function toggle() {
|
|||
const databaseObjectGroup = node.type === "group-tables" || node.type === "group-views" || node.type === "group-materialized-views" || node.type === "group-procedures" || node.type === "group-functions" || node.type === "group-sequences" || node.type === "group-packages";
|
||||
if (databaseObjectGroup && connectionStore.isTreeNodeChildrenLoaded(node.id)) {
|
||||
node.isExpanded = !node.isExpanded;
|
||||
if (wasExpanded) connectionStore.releaseCollapsedTreeNodeChildren(node.id);
|
||||
if (wasExpanded && !connectionStore.sidebarSearchQuery) connectionStore.releaseCollapsedTreeNodeChildren(node.id);
|
||||
emit("node-toggled", node, wasExpanded);
|
||||
return;
|
||||
}
|
||||
|
||||
if (node.isExpanded) {
|
||||
node.isExpanded = false;
|
||||
connectionStore.releaseCollapsedTreeNodeChildren(node.id);
|
||||
if (!connectionStore.sidebarSearchQuery) connectionStore.releaseCollapsedTreeNodeChildren(node.id);
|
||||
emit("node-toggled", node, wasExpanded);
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -58,6 +58,95 @@ describe("connectionStore metadata loading", () => {
|
|||
setActivePinia(createPinia());
|
||||
});
|
||||
|
||||
it("loads missing database roots only for connected sidebar search targets", async () => {
|
||||
const checkConnectionHealth = vi.fn().mockResolvedValue(undefined);
|
||||
const listDatabases = vi.fn().mockResolvedValue([{ name: "dajia", comment: null }]);
|
||||
|
||||
vi.doMock("@/lib/backend/tauriRuntime", () => ({ isTauriRuntime: () => false }));
|
||||
vi.doMock("@/lib/backend/api", () => ({
|
||||
checkConnectionHealth,
|
||||
listDatabases,
|
||||
loadSchemaCache: vi.fn().mockResolvedValue(null),
|
||||
saveSchemaCache: vi.fn().mockResolvedValue(undefined),
|
||||
saveConnections: vi.fn().mockResolvedValue(undefined),
|
||||
saveSidebarLayout: vi.fn().mockResolvedValue(undefined),
|
||||
}));
|
||||
|
||||
const { useConnectionStore } = await import("@/stores/connectionStore");
|
||||
const { filterSidebarTree } = await import("@/lib/sidebar/sidebarSearchTree");
|
||||
const store = useConnectionStore();
|
||||
const active = { ...mysqlConnection(), id: "mysql-active", name: "localhost" };
|
||||
const connected = { ...mysqlConnection(), id: "mysql-connected", name: "PLM-PRO" };
|
||||
const disconnected = { ...mysqlConnection(), id: "mysql-disconnected", name: "offline" };
|
||||
const nodes: TreeNode[] = [
|
||||
{
|
||||
id: active.id,
|
||||
label: active.name,
|
||||
type: "connection",
|
||||
connectionId: active.id,
|
||||
isExpanded: true,
|
||||
children: [{ id: `${active.id}:dajia`, label: "dajia", type: "database", connectionId: active.id, database: "dajia", isExpanded: false }],
|
||||
},
|
||||
{ id: connected.id, label: connected.name, type: "connection", connectionId: connected.id, isExpanded: false, children: [] },
|
||||
{ id: disconnected.id, label: disconnected.name, type: "connection", connectionId: disconnected.id, isExpanded: false, children: [] },
|
||||
];
|
||||
store.connections = [active, connected, disconnected];
|
||||
store.connectedIds = new Set([active.id, connected.id]);
|
||||
store.activeConnectionId = active.id;
|
||||
store.treeNodes = nodes;
|
||||
|
||||
await Promise.all(nodes.map((node) => store.loadConnectedConnectionRootForSidebarSearch(node.connectionId!)));
|
||||
|
||||
expect(listDatabases).toHaveBeenCalledTimes(1);
|
||||
expect(listDatabases).toHaveBeenCalledWith(connected.id);
|
||||
expect(checkConnectionHealth).not.toHaveBeenCalled();
|
||||
expect(store.activeConnectionId).toBe(active.id);
|
||||
expect(nodes.map((node) => node.isExpanded)).toEqual([true, false, false]);
|
||||
expect(filterSidebarTree(nodes, "dajia", new Set()).map((node) => node.id)).toEqual([active.id, connected.id]);
|
||||
});
|
||||
|
||||
it("does not collapse a connection whose normal root load is already in flight", async () => {
|
||||
let resolveDatabases!: (databases: Array<{ name: string; comment: null }>) => void;
|
||||
let markListStarted!: () => void;
|
||||
const listStarted = new Promise<void>((resolve) => {
|
||||
markListStarted = resolve;
|
||||
});
|
||||
const listDatabases = vi.fn(
|
||||
() =>
|
||||
new Promise<Array<{ name: string; comment: null }>>((resolve) => {
|
||||
resolveDatabases = resolve;
|
||||
markListStarted();
|
||||
}),
|
||||
);
|
||||
|
||||
vi.doMock("@/lib/backend/tauriRuntime", () => ({ isTauriRuntime: () => false }));
|
||||
vi.doMock("@/lib/backend/api", () => ({
|
||||
checkConnectionHealth: vi.fn().mockResolvedValue(undefined),
|
||||
listDatabases,
|
||||
loadSchemaCache: vi.fn().mockResolvedValue(null),
|
||||
saveSchemaCache: vi.fn().mockResolvedValue(undefined),
|
||||
saveConnections: vi.fn().mockResolvedValue(undefined),
|
||||
saveSidebarLayout: vi.fn().mockResolvedValue(undefined),
|
||||
}));
|
||||
|
||||
const { useConnectionStore } = await import("@/stores/connectionStore");
|
||||
const store = useConnectionStore();
|
||||
const connection = mysqlConnection();
|
||||
const node: TreeNode = { id: connection.id, label: connection.name, type: "connection", connectionId: connection.id, isExpanded: false, children: [] };
|
||||
store.connections = [connection];
|
||||
store.connectedIds.add(connection.id);
|
||||
store.treeNodes = [node];
|
||||
|
||||
const normalLoad = store.loadDatabases(connection.id);
|
||||
const searchLoad = store.loadConnectedConnectionRootForSidebarSearch(connection.id);
|
||||
await listStarted;
|
||||
resolveDatabases([{ name: "dajia", comment: null }]);
|
||||
await Promise.all([normalLoad, searchLoad]);
|
||||
|
||||
expect(listDatabases).toHaveBeenCalledTimes(1);
|
||||
expect(node.isExpanded).toBe(true);
|
||||
});
|
||||
|
||||
it("renders simple-mode table children without waiting for supplemental objects", async () => {
|
||||
const tables: TableInfo[] = [{ name: "users", table_type: "TABLE", comment: null }];
|
||||
const listTables = vi.fn().mockResolvedValue(tables);
|
||||
|
|
|
|||
|
|
@ -205,6 +205,7 @@ export type TreeClipboard =
|
|||
|
||||
interface LoadTreeOptions {
|
||||
force?: boolean;
|
||||
connectedOnly?: boolean;
|
||||
expectedSidebarSearchQuery?: string;
|
||||
searchFilter?: string;
|
||||
sidebarTableSearchParentId?: string;
|
||||
|
|
@ -2227,7 +2228,11 @@ export const useConnectionStore = defineStore("connection", () => {
|
|||
if (!node) return;
|
||||
node.isLoading = true;
|
||||
try {
|
||||
await ensureConnected(connectionId);
|
||||
if (options?.connectedOnly) {
|
||||
if (!connectedIds.value.has(connectionId)) return;
|
||||
} else {
|
||||
await ensureConnected(connectionId);
|
||||
}
|
||||
if (useCachedChildren(node, options)) return;
|
||||
|
||||
const config = getConfig(connectionId);
|
||||
|
|
@ -2368,6 +2373,25 @@ export const useConnectionStore = defineStore("connection", () => {
|
|||
);
|
||||
}
|
||||
|
||||
async function loadConnectedConnectionRootForSidebarSearch(connectionId: string) {
|
||||
if (!connectedIds.value.has(connectionId)) return;
|
||||
const config = getConfig(connectionId);
|
||||
if (!config || ["redis", "etcd", "zookeeper", "mongodb", "elasticsearch", "milvus", "qdrant", "weaviate", "chromadb", "mq", "nacos"].includes(config.db_type)) return;
|
||||
const node = findNode(treeNodes.value, connectionId);
|
||||
if (!node || node.type !== "connection" || node.isLoading || hasConnectionMetadataChildren(node.children)) return;
|
||||
const scope = { kind: "connection-databases" as const, connectionId, driverProfile: metadataDriverProfile(config) };
|
||||
if (metadataLoadCoordinator.has(scope)) return;
|
||||
|
||||
const wasExpanded = !!node.isExpanded;
|
||||
node.isLoading = true;
|
||||
try {
|
||||
await loadDatabases(connectionId, { connectedOnly: true });
|
||||
} finally {
|
||||
node.isExpanded = wasExpanded;
|
||||
node.isLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function loadRedisDatabases(connectionId: string) {
|
||||
const node = findNode(treeNodes.value, connectionId);
|
||||
if (!node) return;
|
||||
|
|
@ -5369,6 +5393,7 @@ export const useConnectionStore = defineStore("connection", () => {
|
|||
disconnect,
|
||||
closeDatabaseConnection,
|
||||
ensureConnected,
|
||||
loadConnectedConnectionRootForSidebarSearch,
|
||||
isTreeNodeChildrenLoaded,
|
||||
releaseCollapsedTreeNodeChildren,
|
||||
setBeforeConnectHandler,
|
||||
|
|
|
|||
Loading…
Reference in New Issue