fix(sidebar): load visible schema filters on mount

This commit is contained in:
t8y2 2026-07-15 12:53:39 +08:00
parent 1e7a33bd99
commit 86a6846b10
7 changed files with 47 additions and 12 deletions

View File

@ -85,6 +85,7 @@ import { usesSyntheticRowIdKey } from "@/lib/table/tableEditing";
import { tableOpenPageLimit } from "@/lib/table/tableOpenPageLimit";
import { getCachedTableMetadata, loadTableMetadata, TABLE_METADATA_CACHE_TTL_MS, tableMetadataToDataTabMeta } from "@/lib/metadata/tableMetadataCache";
import {
canConfigureVisibleSchemasForTreeNode,
canCreateConnectionNamespace,
canCreateDatabaseNodeNamespace,
canEditDatabaseProperties as canEditDatabasePropertiesForNode,
@ -161,7 +162,6 @@ import { isSqlServerLinkedNode } from "@/lib/database/sqlServerLinkedServers";
import DatabaseIcon from "@/components/icons/DatabaseIcon.vue";
import ConnectionErrorIndicator from "@/components/connection/ConnectionErrorIndicator.vue";
import ProductionContextBadge from "@/components/common/ProductionContextBadge.vue";
import { isSchemaAware } from "@/lib/database/databaseFeatureSupport";
import { Badge } from "@/components/ui/badge";
import { Input } from "@/components/ui/input";
import LightTooltip from "@/components/ui/LightTooltip.vue";
@ -4251,15 +4251,9 @@ const canConfigureVisibleDatabases = computed(() => {
});
const canConfigureVisibleSchemas = computed(() => {
if (props.node.type === "database" && props.node.connectionId && props.node.database != null) {
const dbType = connectionStore.getConfig(props.node.connectionId)?.db_type;
return isSchemaAware(dbType);
}
if (props.node.type === "connection" && props.node.connectionId) {
const dbType = connectionStore.getConfig(props.node.connectionId)?.db_type;
return isSchemaAware(dbType) && !usesTreeSchemaMode(dbType);
}
return false;
if (!props.node.connectionId) return false;
const dbType = connectionStore.getConfig(props.node.connectionId)?.db_type;
return canConfigureVisibleSchemasForTreeNode(dbType, props.node.type, props.node.database);
});
const canCopyFinalProxyPort = computed(() => {

View File

@ -67,6 +67,8 @@ watch(
if (!open) return;
loadDatabases().catch(() => {});
},
// Tree-level async hosts mount the dialog with `open` already true.
{ immediate: true },
);
watch(showSystemDatabases, (show) => {

View File

@ -64,6 +64,8 @@ watch(
loadSchemas().catch(() => {});
}
},
// Tree-level async hosts mount the dialog with `open` already true.
{ immediate: true },
);
function initDraftMode() {

View File

@ -40,6 +40,12 @@ export function usesTreeSchemaMode(dbType?: DatabaseType): boolean {
return !!dbType && TREE_SCHEMA_TYPES.has(dbType);
}
export function canConfigureVisibleSchemasForTreeNode(dbType: DatabaseType | undefined, nodeType: TreeNodeType, database?: string | null): boolean {
if (!isSchemaAware(dbType)) return false;
if (nodeType === "database") return database != null;
return nodeType === "connection" && !usesTreeSchemaMode(dbType);
}
export function usesDatabaseObjectTreeMode(dbType?: DatabaseType): boolean {
return !!dbType && DATABASE_OBJECT_TREE_TYPES.has(dbType);
}

View File

@ -1,7 +1,7 @@
import assert from "node:assert/strict";
import { test } from "vitest";
import { appendVisibleDatabaseSelection, buildDraftVisibleDatabasesConnectionId, connectionCanChooseVisibleDatabases, visibleDatabaseSelectionIsStale, initialVisibleDatabaseSelection } from "../../apps/desktop/src/lib/connection/connectionVisibleDatabases.ts";
import { connectionUsesVisibleSchemaFilter, filterDatabaseNamesForConnection, filterDatabaseNamesForVisiblePicker } from "../../apps/desktop/src/lib/database/visibleDatabases.ts";
import { connectionUsesVisibleSchemaFilter, filterDatabaseNamesForConnection, filterDatabaseNamesForVisiblePicker, filterSchemaNamesForConnection, normalizeVisibleSchemaSelection } from "../../apps/desktop/src/lib/database/visibleDatabases.ts";
import type { ConnectionConfig } from "../../apps/desktop/src/types/database.ts";
function config(overrides: Partial<ConnectionConfig> = {}): ConnectionConfig {
@ -95,6 +95,14 @@ test("OceanBase Oracle uses schema filtering for visible object selection", () =
assert.equal(connectionUsesVisibleSchemaFilter(config({ db_type: "mysql", driver_profile: "oceanbase" })), false);
});
test("Vastbase schema filters preserve ordinary schemas and explicit empty selections", () => {
const schemas = ["public", "app"];
assert.deepEqual(filterSchemaNamesForConnection(schemas, config({ db_type: "vastbase", database: "vastbase" }), "vastbase"), schemas);
assert.deepEqual(filterSchemaNamesForConnection(schemas, config({ db_type: "vastbase", database: "vastbase", visible_schemas: { vastbase: [] } }), "vastbase"), []);
assert.deepEqual(normalizeVisibleSchemaSelection([], schemas), []);
assert.deepEqual(normalizeVisibleSchemaSelection(["app", "missing", "app", "public"], schemas), ["app", "public"]);
});
test("Dameng default SYSDBA user remains selectable", () => {
assert.deepEqual(filterDatabaseNamesForConnection(["SYS", "SYSDBA", "SYSAUDITOR"], config({ db_type: "dameng" })), ["SYSDBA"]);
});

View File

@ -1,6 +1,6 @@
import assert from "node:assert/strict";
import { test } from "vitest";
import { isSchemaAware, supportsClearableQuerySchema, supportsDatabaseCreation, usesTreeSchemaMode } from "../../apps/desktop/src/lib/database/databaseCapabilities.ts";
import { canConfigureVisibleSchemasForTreeNode, isSchemaAware, supportsClearableQuerySchema, supportsDatabaseCreation, usesTreeSchemaMode } from "../../apps/desktop/src/lib/database/databaseCapabilities.ts";
test("TDengine uses database/catalog tree nodes without a schema layer", () => {
assert.equal(isSchemaAware("tdengine"), false);
@ -17,6 +17,22 @@ test("GoldenDB and Vastbase expose database creation", () => {
assert.equal(supportsDatabaseCreation("vastbase"), true);
});
test("Vastbase keeps PostgreSQL-like schema filtering on database nodes", () => {
assert.equal(isSchemaAware("vastbase"), true);
assert.equal(usesTreeSchemaMode("vastbase"), true);
assert.equal(canConfigureVisibleSchemasForTreeNode("vastbase", "database", "vastbase"), true);
assert.equal(canConfigureVisibleSchemasForTreeNode("vastbase", "connection"), false);
});
test("visible schema menu capability preserves adjacent database families", () => {
assert.equal(canConfigureVisibleSchemasForTreeNode("oracle", "connection"), true);
assert.equal(canConfigureVisibleSchemasForTreeNode("dameng", "connection"), true);
assert.equal(canConfigureVisibleSchemasForTreeNode("oceanbase-oracle", "connection"), false);
assert.equal(canConfigureVisibleSchemasForTreeNode("postgres", "database", "app"), true);
assert.equal(canConfigureVisibleSchemasForTreeNode("sqlserver", "database", "app"), true);
assert.equal(canConfigureVisibleSchemasForTreeNode("mysql", "database", "app"), false);
});
test("only Oracle-compatible single-database schemas can be cleared from query tabs", () => {
assert.equal(supportsClearableQuerySchema("oracle"), true);
assert.equal(supportsClearableQuerySchema("dameng"), true);

View File

@ -6,6 +6,8 @@ const connectionTree = readFileSync("apps/desktop/src/components/sidebar/Connect
const treeItem = readFileSync("apps/desktop/src/components/sidebar/TreeItem.vue", "utf8");
const dialogHost = readFileSync("apps/desktop/src/components/sidebar/SidebarTreeItemDialogs.vue", "utf8");
const dialogState = readFileSync("apps/desktop/src/components/sidebar/sidebarTreeDialogState.ts", "utf8");
const visibleDatabasesDialog = readFileSync("apps/desktop/src/components/sidebar/VisibleDatabasesDialog.vue", "utf8");
const visibleSchemasDialog = readFileSync("apps/desktop/src/components/sidebar/VisibleSchemasDialog.vue", "utf8");
function occurrences(source: string, value: string): number {
return source.split(value).length - 1;
@ -43,6 +45,11 @@ test("saved object dialogs refresh the immutable active target", () => {
assert.match(connectionTree, /findSidebarActionTarget\(store\.treeNodes, target\)/);
});
test("lazy visible object dialogs load when first mounted open", () => {
assert.match(visibleDatabasesDialog, /watch\([\s\S]*?loadDatabases\(\)[\s\S]*?\{ immediate: true \}/);
assert.match(visibleSchemasDialog, /watch\([\s\S]*?loadSchemas\(\)[\s\S]*?\{ immediate: true \}/);
});
test("shared dialog state is owner-gated and confirm handlers use snapshots", () => {
assert.match(dialogState, /export const sidebarTreeDialogOwner = shallowRef<symbol \| null>\(null\)/);
assert.match(dialogState, /export const sidebarDangerTarget = shallowRef<TreeNode \| null>\(null\)/);