feat(query): choose new query context
This commit is contained in:
parent
9d7ca42acf
commit
f5af9da7c7
|
|
@ -29,6 +29,7 @@ import "@/i18n";
|
|||
import { translateBackendError } from "@/i18n/backend-errors";
|
||||
import * as api from "@/lib/api";
|
||||
import { resolveDefaultDatabase } from "@/lib/defaultDatabase";
|
||||
import { findTreeNodeById, resolveNewQueryTarget } from "@/lib/newQueryContext";
|
||||
import { buildExecutableObjectSourceStatements, objectSourceSaveExecutionMode } from "@/lib/objectSourceEditor";
|
||||
import { resolveExecutableSql } from "@/lib/sqlExecutionTarget";
|
||||
import { isTauriRuntime } from "@/lib/tauriRuntime";
|
||||
|
|
@ -114,6 +115,7 @@ const selectedSql = ref("");
|
|||
const cursorPos = ref(0);
|
||||
const formatSqlRequestId = ref(0);
|
||||
const activeOutputView = ref<"result" | "explain" | "chart">("result");
|
||||
const newQueryContextSource = ref<"tab" | "sidebar">("tab");
|
||||
const showSaveSqlDialog = ref(false);
|
||||
const saveSqlName = ref("");
|
||||
const saveSqlFolderId = ref("");
|
||||
|
|
@ -206,6 +208,7 @@ const saveSqlFolders = computed(() => {
|
|||
watch(
|
||||
() => queryStore.activeTabId,
|
||||
(id) => {
|
||||
if (id) newQueryContextSource.value = "tab";
|
||||
selectedSql.value = "";
|
||||
activeOutputView.value = "result";
|
||||
showDriverStore.value = false;
|
||||
|
|
@ -213,6 +216,13 @@ watch(
|
|||
},
|
||||
);
|
||||
|
||||
watch(
|
||||
() => connectionStore.selectedTreeNodeId,
|
||||
(id) => {
|
||||
if (id) newQueryContextSource.value = "sidebar";
|
||||
},
|
||||
);
|
||||
|
||||
function toggleAiPanel() {
|
||||
showAiPanel.value = !showAiPanel.value;
|
||||
localStorage.setItem("dbx-ai-panel-open", String(showAiPanel.value));
|
||||
|
|
@ -435,16 +445,24 @@ function setConnectionDialogOpen(value: boolean) {
|
|||
}
|
||||
|
||||
async function newQuery() {
|
||||
const connId = connectionStore.activeConnectionId || connectionStore.connections[0]?.id;
|
||||
if (!connId) return;
|
||||
const conn = connectionStore.getConfig(connId);
|
||||
const target = resolveNewQueryTarget({
|
||||
activeTab: activeTab.value,
|
||||
selectedTreeNode: findTreeNodeById(connectionStore.treeNodes, connectionStore.selectedTreeNodeId),
|
||||
activeConnectionId: connectionStore.activeConnectionId,
|
||||
connections: connectionStore.connections,
|
||||
preferredSource: newQueryContextSource.value,
|
||||
});
|
||||
if (!target) return;
|
||||
const conn = connectionStore.getConfig(target.connectionId);
|
||||
if (!conn) return;
|
||||
connectionStore.activeConnectionId = connId;
|
||||
const tabId = queryStore.createTab(conn.id, resolveDefaultDatabase(conn, []));
|
||||
connectionStore.activeConnectionId = target.connectionId;
|
||||
const tabId = queryStore.createTab(conn.id, target.database);
|
||||
try {
|
||||
await connectionStore.ensureConnected(connId);
|
||||
const options = await getDatabaseOptions(connId);
|
||||
queryStore.updateDatabase(tabId, resolveDefaultDatabase(conn, options));
|
||||
await connectionStore.ensureConnected(target.connectionId);
|
||||
if (target.shouldRefreshDefaultDatabase) {
|
||||
const options = await getDatabaseOptions(target.connectionId);
|
||||
queryStore.updateDatabase(tabId, resolveDefaultDatabase(conn, options));
|
||||
}
|
||||
} catch (e: any) {
|
||||
toast(t("connection.connectFailed", { message: translateBackendError(t, e?.message || String(e)) }), 5000);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,64 @@
|
|||
import { resolveDefaultDatabase } from "@/lib/defaultDatabase";
|
||||
import type { ConnectionConfig, QueryTab, TreeNode } from "@/types/database";
|
||||
|
||||
export interface NewQueryTarget {
|
||||
connectionId: string;
|
||||
database: string;
|
||||
shouldRefreshDefaultDatabase: boolean;
|
||||
}
|
||||
|
||||
export type NewQueryContextSource = "tab" | "sidebar";
|
||||
|
||||
interface ResolveNewQueryTargetInput {
|
||||
activeTab?: Pick<QueryTab, "connectionId" | "database">;
|
||||
selectedTreeNode?: Pick<TreeNode, "connectionId" | "database"> | null;
|
||||
activeConnectionId?: string | null;
|
||||
connections: Pick<ConnectionConfig, "id" | "database">[];
|
||||
preferredSource?: NewQueryContextSource;
|
||||
}
|
||||
|
||||
export function findTreeNodeById(nodes: TreeNode[], id: string | null | undefined): TreeNode | null {
|
||||
if (!id) return null;
|
||||
for (const node of nodes) {
|
||||
if (node.id === id) return node;
|
||||
const found = findTreeNodeById(node.children || [], id);
|
||||
if (found) return found;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function resolveNewQueryTarget(input: ResolveNewQueryTargetInput): NewQueryTarget | null {
|
||||
const primaryContext = input.preferredSource === "sidebar" ? input.selectedTreeNode || undefined : input.activeTab;
|
||||
const secondaryContext = input.preferredSource === "sidebar" ? input.activeTab : input.selectedTreeNode || undefined;
|
||||
const primaryTarget = targetFromContext(primaryContext, input.connections);
|
||||
if (primaryTarget) return primaryTarget;
|
||||
const secondaryTarget = targetFromContext(secondaryContext, input.connections);
|
||||
if (secondaryTarget) return secondaryTarget;
|
||||
|
||||
const activeConnection = input.activeConnectionId
|
||||
? input.connections.find((connection) => connection.id === input.activeConnectionId)
|
||||
: undefined;
|
||||
const fallbackConnection = activeConnection || input.connections[0];
|
||||
return fallbackConnection
|
||||
? {
|
||||
connectionId: fallbackConnection.id,
|
||||
database: resolveDefaultDatabase(fallbackConnection, []),
|
||||
shouldRefreshDefaultDatabase: true,
|
||||
}
|
||||
: null;
|
||||
}
|
||||
|
||||
function targetFromContext(
|
||||
context: Pick<QueryTab | TreeNode, "connectionId" | "database"> | undefined,
|
||||
connections: Pick<ConnectionConfig, "id" | "database">[],
|
||||
): NewQueryTarget | null {
|
||||
if (!context?.connectionId) return null;
|
||||
const connection = connections.find((item) => item.id === context.connectionId);
|
||||
if (!connection) return null;
|
||||
const database = context.database || resolveDefaultDatabase(connection, []);
|
||||
return {
|
||||
connectionId: context.connectionId,
|
||||
database,
|
||||
shouldRefreshDefaultDatabase: !context.database,
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,113 @@
|
|||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
import { resolveNewQueryTarget } from "../../apps/desktop/src/lib/newQueryContext.ts";
|
||||
import type { ConnectionConfig, QueryTab, TreeNode } from "../../apps/desktop/src/types/database.ts";
|
||||
|
||||
function connection(id: string, database = ""): ConnectionConfig {
|
||||
return {
|
||||
id,
|
||||
name: id,
|
||||
db_type: "postgres",
|
||||
host: "localhost",
|
||||
port: 5432,
|
||||
username: "postgres",
|
||||
password: "",
|
||||
database,
|
||||
};
|
||||
}
|
||||
|
||||
function queryTab(connectionId: string, database: string, mode: QueryTab["mode"] = "data"): QueryTab {
|
||||
return {
|
||||
id: `${connectionId}-${database}`,
|
||||
title: "users",
|
||||
connectionId,
|
||||
database,
|
||||
sql: "",
|
||||
isExecuting: false,
|
||||
isCancelling: false,
|
||||
isExplaining: false,
|
||||
mode,
|
||||
};
|
||||
}
|
||||
|
||||
test("new query target prefers the active data tab context", () => {
|
||||
const target = resolveNewQueryTarget({
|
||||
activeTab: queryTab("conn-data", "analytics", "data"),
|
||||
selectedTreeNode: {
|
||||
id: "conn-tree:reporting",
|
||||
label: "reporting",
|
||||
type: "database",
|
||||
connectionId: "conn-tree",
|
||||
database: "reporting",
|
||||
},
|
||||
activeConnectionId: "conn-active",
|
||||
connections: [connection("conn-active"), connection("conn-data"), connection("conn-tree")],
|
||||
});
|
||||
|
||||
assert.deepEqual(target, {
|
||||
connectionId: "conn-data",
|
||||
database: "analytics",
|
||||
shouldRefreshDefaultDatabase: false,
|
||||
});
|
||||
});
|
||||
|
||||
test("new query target uses the selected sidebar node when there is no active tab", () => {
|
||||
const selectedTreeNode: TreeNode = {
|
||||
id: "conn-tree:reporting:public:users",
|
||||
label: "users",
|
||||
type: "table",
|
||||
connectionId: "conn-tree",
|
||||
database: "reporting",
|
||||
schema: "public",
|
||||
};
|
||||
|
||||
const target = resolveNewQueryTarget({
|
||||
activeTab: undefined,
|
||||
selectedTreeNode,
|
||||
activeConnectionId: "conn-active",
|
||||
connections: [connection("conn-active"), connection("conn-tree")],
|
||||
});
|
||||
|
||||
assert.deepEqual(target, {
|
||||
connectionId: "conn-tree",
|
||||
database: "reporting",
|
||||
shouldRefreshDefaultDatabase: false,
|
||||
});
|
||||
});
|
||||
|
||||
test("new query target prefers the selected sidebar node after sidebar focus", () => {
|
||||
const target = resolveNewQueryTarget({
|
||||
activeTab: queryTab("conn-data", "analytics", "data"),
|
||||
selectedTreeNode: {
|
||||
id: "conn-tree:reporting:public:users",
|
||||
label: "users",
|
||||
type: "table",
|
||||
connectionId: "conn-tree",
|
||||
database: "reporting",
|
||||
},
|
||||
activeConnectionId: "conn-active",
|
||||
connections: [connection("conn-active"), connection("conn-data"), connection("conn-tree")],
|
||||
preferredSource: "sidebar",
|
||||
});
|
||||
|
||||
assert.deepEqual(target, {
|
||||
connectionId: "conn-tree",
|
||||
database: "reporting",
|
||||
shouldRefreshDefaultDatabase: false,
|
||||
});
|
||||
});
|
||||
|
||||
test("new query target refreshes default database for connection-only sidebar nodes", () => {
|
||||
const target = resolveNewQueryTarget({
|
||||
activeTab: undefined,
|
||||
selectedTreeNode: { id: "conn-tree", label: "conn-tree", type: "connection", connectionId: "conn-tree" },
|
||||
activeConnectionId: "conn-active",
|
||||
connections: [connection("conn-active"), connection("conn-tree", "saved_default")],
|
||||
});
|
||||
|
||||
assert.deepEqual(target, {
|
||||
connectionId: "conn-tree",
|
||||
database: "saved_default",
|
||||
shouldRefreshDefaultDatabase: true,
|
||||
});
|
||||
});
|
||||
Loading…
Reference in New Issue