feat(sidebar): show connection group paths
This commit is contained in:
14 files changed
+251
-10
No files matched your search
@@ -0,0 +1,37 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { findConnectionGroupPath } from "@/lib/sidebar/sidebarLayout";
|
||||
import type { SidebarLayout } from "@/types/database";
|
||||
|
||||
const layout: SidebarLayout = {
|
||||
groups: [
|
||||
{ id: "project", name: "Project", collapsed: false },
|
||||
{ id: "staging", name: "Staging", collapsed: false },
|
||||
],
|
||||
order: [
|
||||
{
|
||||
type: "group",
|
||||
id: "project",
|
||||
children: [
|
||||
{
|
||||
type: "group",
|
||||
id: "staging",
|
||||
children: [{ type: "connection", id: "nested" }],
|
||||
},
|
||||
{ type: "connection", id: "grouped" },
|
||||
],
|
||||
},
|
||||
{ type: "connection", id: "root" },
|
||||
],
|
||||
};
|
||||
|
||||
describe("findConnectionGroupPath", () => {
|
||||
it("returns every containing group from root to leaf", () => {
|
||||
expect(findConnectionGroupPath(layout, "nested")).toEqual(["Project", "Staging"]);
|
||||
expect(findConnectionGroupPath(layout, "grouped")).toEqual(["Project"]);
|
||||
});
|
||||
|
||||
it("distinguishes a top-level connection from a missing connection", () => {
|
||||
expect(findConnectionGroupPath(layout, "root")).toEqual([]);
|
||||
expect(findConnectionGroupPath(layout, "missing")).toBeNull();
|
||||
});
|
||||
});
|
||||
+63
-3
@@ -1,6 +1,28 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { middleEllipsis, queryResultBaseSql, queryResultExecutionSql, resultSourceRange, tabularResultItems } from "@/lib/tabs/tabPresentation";
|
||||
import type { QueryTab } from "@/types/database";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { createPinia, setActivePinia } from "pinia";
|
||||
import { useConnectionStore } from "@/stores/connectionStore";
|
||||
import { connectionGroupDisplayName, middleEllipsis, queryResultBaseSql, queryResultExecutionSql, resultSourceRange, tabTooltipLines, tabularResultItems } from "@/lib/tabs/tabPresentation";
|
||||
import type { ConnectionConfig, QueryTab } from "@/types/database";
|
||||
|
||||
const translations: Record<string, string> = {
|
||||
"tabs.tooltipConnection": "Connection:",
|
||||
"tabs.tooltipGroup": "Group:",
|
||||
"tabs.tooltipDatabase": "Database:",
|
||||
"connectionGroup.ungroupedLabel": "Ungrouped",
|
||||
"editor.noDatabase": "No database",
|
||||
};
|
||||
|
||||
const translate = (key: string) => translations[key] ?? key;
|
||||
|
||||
beforeEach(() => {
|
||||
const values = new Map<string, string>();
|
||||
vi.stubGlobal("localStorage", {
|
||||
getItem: vi.fn((key: string) => values.get(key) ?? null),
|
||||
setItem: vi.fn((key: string, value: string) => values.set(key, value)),
|
||||
removeItem: vi.fn((key: string) => values.delete(key)),
|
||||
});
|
||||
setActivePinia(createPinia());
|
||||
});
|
||||
|
||||
function queryTab(overrides: Partial<QueryTab>): QueryTab {
|
||||
return {
|
||||
@@ -110,6 +132,44 @@ describe("query result labels", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("tab group presentation", () => {
|
||||
it("adds the full, live group path to tab tooltips", () => {
|
||||
const store = useConnectionStore();
|
||||
store.connections = [{ id: "conn-1", name: "PostgreSQL", db_type: "postgres", database: "app" } as ConnectionConfig];
|
||||
store.sidebarLayout = {
|
||||
groups: [
|
||||
{ id: "project", name: "Project", collapsed: false },
|
||||
{ id: "staging", name: "Staging", collapsed: false },
|
||||
],
|
||||
order: [
|
||||
{
|
||||
type: "group",
|
||||
id: "project",
|
||||
children: [{ type: "group", id: "staging", children: [{ type: "connection", id: "conn-1" }] }],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
expect(connectionGroupDisplayName("conn-1", translate)).toBe("Project / Staging");
|
||||
expect(tabTooltipLines(queryTab({ database: "app" }), translate)).toEqual([
|
||||
{ label: "Connection:", value: "PostgreSQL" },
|
||||
{ label: "Group:", value: "Project / Staging" },
|
||||
{ label: "Database:", value: "app" },
|
||||
]);
|
||||
});
|
||||
|
||||
it("labels a top-level connection as ungrouped", () => {
|
||||
const store = useConnectionStore();
|
||||
store.connections = [{ id: "conn-1", name: "PostgreSQL", db_type: "postgres", database: "app" } as ConnectionConfig];
|
||||
store.sidebarLayout = {
|
||||
groups: [],
|
||||
order: [{ type: "connection", id: "conn-1" }],
|
||||
};
|
||||
|
||||
expect(connectionGroupDisplayName("conn-1", translate)).toBe("Ungrouped");
|
||||
});
|
||||
});
|
||||
|
||||
describe("query result source ranges", () => {
|
||||
it("prefers the preserved editor range for a selected duplicate statement", () => {
|
||||
const sql = "SELECT * FROM users;\nSELECT * FROM users;";
|
||||
|
||||
@@ -135,6 +135,53 @@ export function findConnectionLocation(layout: SidebarLayout, connectionId: stri
|
||||
return visit(layout.order);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the display-name path for a connection's containing groups.
|
||||
* A top-level connection returns an empty path; an absent connection returns null.
|
||||
*/
|
||||
export function findConnectionGroupPath(layout: SidebarLayout, connectionId: string): string[] | null {
|
||||
const groupMap = new Map(layout.groups.map((group) => [group.id, group]));
|
||||
|
||||
const visit = (entries: SidebarOrderEntry[], path: string[]): string[] | null => {
|
||||
for (const entry of entries) {
|
||||
if (entry.type === "connection") {
|
||||
if (entry.id === connectionId) return path;
|
||||
continue;
|
||||
}
|
||||
|
||||
const group = groupMap.get(entry.id);
|
||||
if (!group) continue;
|
||||
const found = visit(entryChildren(entry), [...path, group.name]);
|
||||
if (found) return found;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
return visit(layout.order, []);
|
||||
}
|
||||
|
||||
/** Build all connection group paths in one traversal for list rendering. */
|
||||
export function buildConnectionGroupPathMap(layout: SidebarLayout): Map<string, string[]> {
|
||||
const groupMap = new Map(layout.groups.map((group) => [group.id, group]));
|
||||
const paths = new Map<string, string[]>();
|
||||
|
||||
const visit = (entries: SidebarOrderEntry[], path: string[]) => {
|
||||
for (const entry of entries) {
|
||||
if (entry.type === "connection") {
|
||||
paths.set(entry.id, path);
|
||||
continue;
|
||||
}
|
||||
|
||||
const group = groupMap.get(entry.id);
|
||||
if (!group) continue;
|
||||
visit(entryChildren(entry), [...path, group.name]);
|
||||
}
|
||||
};
|
||||
|
||||
visit(layout.order, []);
|
||||
return paths;
|
||||
}
|
||||
|
||||
function findGroupEntry(entries: SidebarOrderEntry[], groupId: string): Extract<SidebarOrderEntry, { type: "group" }> | null {
|
||||
for (const entry of entries) {
|
||||
if (entry.type !== "group") continue;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useConnectionStore } from "@/stores/connectionStore";
|
||||
import { useSettingsStore } from "@/stores/settingsStore";
|
||||
import { findConnectionGroupPath } from "@/lib/sidebar/sidebarLayout";
|
||||
import { splitMongoCommandRanges } from "@/lib/mongo/mongoShellCommand";
|
||||
import { executableStatementRanges, splitSqlStatementRanges, type SqlTextRange } from "@/lib/sql/sqlStatementRanges";
|
||||
import type { ConnectionConfig, DatabaseType, QueryResult, QueryTab } from "@/types/database";
|
||||
@@ -12,6 +13,13 @@ export function connectionDisplayName(connectionId: string): string {
|
||||
return connectionStore.getConfig(connectionId)?.name || connectionId;
|
||||
}
|
||||
|
||||
export function connectionGroupDisplayName(connectionId: string, t: Translate): string | undefined {
|
||||
const connectionStore = useConnectionStore();
|
||||
const path = findConnectionGroupPath(connectionStore.sidebarLayout, connectionId);
|
||||
if (path === null) return undefined;
|
||||
return path.join(" / ") || t("connectionGroup.ungroupedLabel");
|
||||
}
|
||||
|
||||
export function connectionColor(connectionId: string): string {
|
||||
const connectionStore = useConnectionStore();
|
||||
return connectionStore.getConfig(connectionId)?.color || "";
|
||||
@@ -111,11 +119,9 @@ export function tabDisplayTitle(tab: QueryTab, t: Translate): string {
|
||||
|
||||
export function tabTooltipLines(tab: QueryTab, t: Translate): { label: string; value: string }[] {
|
||||
const connName = connectionDisplayName(tab.connectionId);
|
||||
const groupName = connectionGroupDisplayName(tab.connectionId, t);
|
||||
const database = databaseDisplayNameForTab(tab.connectionId, tab.database, t);
|
||||
const lines: { label: string; value: string }[] = [
|
||||
{ label: t("tabs.tooltipConnection"), value: connName },
|
||||
{ label: t("tabs.tooltipDatabase"), value: database },
|
||||
];
|
||||
const lines: { label: string; value: string }[] = [{ label: t("tabs.tooltipConnection"), value: connName }, ...(groupName ? [{ label: t("tabs.tooltipGroup"), value: groupName }] : []), { label: t("tabs.tooltipDatabase"), value: database }];
|
||||
if (tab.mode === "query" && queryTitle(tab)) {
|
||||
lines.unshift({ label: t("tabs.tooltipTitle"), value: tab.title });
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user