diff --git a/frontend/src/components/Sidebar.locate-toolbar.test.tsx b/frontend/src/components/Sidebar.locate-toolbar.test.tsx index 061af512..18680dab 100644 --- a/frontend/src/components/Sidebar.locate-toolbar.test.tsx +++ b/frontend/src/components/Sidebar.locate-toolbar.test.tsx @@ -1034,6 +1034,8 @@ describe('Sidebar locate toolbar', () => { expect(css).toMatch(/\.gn-v2-tree-title \{[^}]*font-size: var\(--gn-sidebar-tree-font-size, var\(--gn-font-size-sm, 12px\)\);/s); expect(css).toMatch(/\.gn-v2-tree-title\.is-mono \.gn-v2-tree-label \{[^}]*font-size: inherit;[^}]*font-weight: 400 !important;/s); expect(css).toMatch(/\.gn-v2-tree-count \{[^}]*font-size: clamp\(10px, calc\(var\(--gn-sidebar-tree-font-size, var\(--gn-font-size-sm, 12px\)\) - 1px\), 16px\);/s); + expect(css).toMatch(/\.gn-v2-tree-title\.is-redis-db \.gn-v2-tree-label \{[^}]*display: inline-flex;[^}]*gap: 6px;/s); + expect(css).toMatch(/\.gn-v2-redis-db-alias \{[^}]*color: var\(--gn-fg-5\);[^}]*opacity: 0\.78;/s); expect(css).toMatch(/\.gn-v2-connection-rail \{[^}]*width: calc\(38px \* var\(--gn-ui-scale, 1\)\);[^}]*flex: 0 0 calc\(38px \* var\(--gn-ui-scale, 1\)\);/s); expect(css).toMatch(/\.gn-v2-rail-item,\s*body\[data-ui-version="v2"\] \.gn-v2-rail-tool \{[^}]*width: calc\(36px \* var\(--gn-ui-scale, 1\)\);[^}]*height: calc\(38px \* var\(--gn-ui-scale, 1\)\);[^}]*font-size: var\(--gn-font-size-sm, 12px\);/s); expect(css).toMatch(/\.gn-v2-rail-tool \{[^}]*height: calc\(32px \* var\(--gn-ui-scale, 1\)\);/s); @@ -2543,6 +2545,25 @@ describe('Sidebar locate toolbar', () => { expect(source).toContain("title: buildConnectionRootRedisMonitorTabTitle(`db${redisDB}`)"); }); + it('keeps redis db key counts in v2 meta and renders aliases separately from the db title', () => { + const loaderSource = readSourceFile('./sidebar/useSidebarTreeLoaders.tsx'); + const redisLoadStart = loaderSource.indexOf("if (conn.config.type === 'redis') {"); + const redisLoadEnd = loaderSource.indexOf('const res = await DBGetDatabases', redisLoadStart); + expect(redisLoadStart).toBeGreaterThanOrEqual(0); + expect(redisLoadEnd).toBeGreaterThan(redisLoadStart); + const redisLoadSource = loaderSource.slice(redisLoadStart, redisLoadEnd); + + expect(redisLoadSource).toContain('const alias = getRedisDbAlias(redisDbAliases, conn.id, db.index);'); + expect(redisLoadSource).toContain('title: buildRedisDbNodeLabel('); + expect(redisLoadSource).toContain('dataRef: { ...conn, redisDB: db.index, redisKeyCount: keyCount, redisDbAlias: alias }'); + expect(redisLoadSource).not.toContain("keyCount > 0 ? ` (${keyCount})` : ''"); + + const titleSource = readSourceFile('./sidebar/SidebarTreeTitle.tsx'); + expect(titleSource).toContain("node.type === 'redis-db' ? 'is-redis-db' : ''"); + expect(titleSource).toContain("const redisDbAlias = node.type === 'redis-db'"); + expect(titleSource).toContain('className="gn-v2-redis-db-alias"'); + }); + it('localizes sidebar JVM probe and resource failure prompts', () => { const source = readSidebarSource(); diff --git a/frontend/src/components/sidebar/SidebarTreeTitle.tsx b/frontend/src/components/sidebar/SidebarTreeTitle.tsx index b4cf3a36..516d4f31 100644 --- a/frontend/src/components/sidebar/SidebarTreeTitle.tsx +++ b/frontend/src/components/sidebar/SidebarTreeTitle.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { StarFilled, StarOutlined } from '@ant-design/icons'; import { t } from '../../i18n'; import { SIDEBAR_SQL_EDITOR_DRAG_MIME, encodeSidebarSqlEditorDragPayload } from '../../utils/sidebarSqlDrag'; +import { sanitizeRedisDbAlias } from '../../utils/redisDbAlias'; import { resolveSidebarObjectDragText } from '../sidebarCoreUtils'; import { resolveV2ObjectGroupTitle } from './sidebarHelpers'; @@ -52,6 +53,11 @@ export const renderSidebarV2TreeTitle = ({ return rawTitle; })(); const metaText = getV2TreeMetaText(node); + const redisDbAlias = node.type === 'redis-db' + ? sanitizeRedisDbAlias(node?.dataRef?.redisDbAlias) + : ''; + const redisDbIndex = Number(node?.dataRef?.redisDB); + const redisDbBaseTitle = Number.isFinite(redisDbIndex) ? `db${redisDbIndex}` : displayTitle; const isMono = node.type === 'table' || node.type === 'view' || node.type === 'materialized-view' @@ -66,6 +72,7 @@ export const renderSidebarV2TreeTitle = ({ 'gn-v2-tree-title', isMono ? 'is-mono' : '', node.type === 'object-group' ? 'is-group' : '', + node.type === 'redis-db' ? 'is-redis-db' : '', node.type === 'table' && node?.dataRef?.pinnedSidebarTable ? 'is-pinned-table' : '', ].filter(Boolean).join(' '); const tablePinAction = node.type === 'table' ? ( @@ -145,7 +152,14 @@ export const renderSidebarV2TreeTitle = ({ } : undefined} > {statusBadge} - {displayTitle} + + {redisDbAlias ? ( + <> + {redisDbBaseTitle} + {redisDbAlias} + + ) : displayTitle} + {metaText && {metaText}} {tablePinAction} diff --git a/frontend/src/components/sidebar/sidebarLegacyNodeMenu.tsx b/frontend/src/components/sidebar/sidebarLegacyNodeMenu.tsx index 6129962d..0d0ab1ab 100644 --- a/frontend/src/components/sidebar/sidebarLegacyNodeMenu.tsx +++ b/frontend/src/components/sidebar/sidebarLegacyNodeMenu.tsx @@ -38,17 +38,25 @@ import { } from '../../utils/redisDbAlias'; import { supportsTableTruncateAction } from '../tableDataDangerActions'; -const updateTreeNodeTitle = ( +const updateRedisDbNodeAlias = ( nodes: any[], targetKey: string, title: string, + alias: string, ): any[] => nodes.map((node) => { if (node.key === targetKey) { - return { ...node, title }; + return { + ...node, + title, + dataRef: { + ...(node.dataRef || {}), + redisDbAlias: alias, + }, + }; } if (Array.isArray(node.children)) { - return { ...node, children: updateTreeNodeTitle(node.children, targetKey, title) }; + return { ...node, children: updateRedisDbNodeAlias(node.children, targetKey, title, alias) }; } return node; }); @@ -57,7 +65,7 @@ const openRedisDbAliasModal = ( node: any, context: SidebarLegacyNodeMenuContext, ): void => { - const { id, redisDB, redisKeyCount } = node.dataRef; + const { id, redisDB } = node.dataRef; const { treeDataRef, setTreeData } = context; const currentAlias = getRedisDbAlias( useStore.getState().appearance.redisDbAliases, @@ -91,10 +99,8 @@ const openRedisDbAliasModal = ( id, redisDB, ); - const keyCount = Number(redisKeyCount); - const suffix = Number.isFinite(keyCount) && keyCount > 0 ? ` (${keyCount})` : ''; - const nextTitle = buildRedisDbNodeLabel(redisDB, nextAlias, suffix); - const nextTree = updateTreeNodeTitle(treeDataRef.current, node.key, nextTitle); + const nextTitle = buildRedisDbNodeLabel(redisDB, nextAlias); + const nextTree = updateRedisDbNodeAlias(treeDataRef.current, node.key, nextTitle, nextAlias); treeDataRef.current = nextTree; setTreeData(nextTree); } diff --git a/frontend/src/components/sidebar/useSidebarTreeLoaders.tsx b/frontend/src/components/sidebar/useSidebarTreeLoaders.tsx index 931d2e13..fb598766 100644 --- a/frontend/src/components/sidebar/useSidebarTreeLoaders.tsx +++ b/frontend/src/components/sidebar/useSidebarTreeLoaders.tsx @@ -314,16 +314,16 @@ export const useSidebarTreeLoaders = ({ const redisDbAliases = useStore.getState().appearance.redisDbAliases; let dbs = redisRows.map((db: any) => { const keyCount = Number(db.keys) > 0 ? Number(db.keys) : 0; + const alias = getRedisDbAlias(redisDbAliases, conn.id, db.index); return { title: buildRedisDbNodeLabel( db.index, - getRedisDbAlias(redisDbAliases, conn.id, db.index), - keyCount > 0 ? ` (${keyCount})` : '', + alias, ), key: `${conn.id}-db${db.index}`, icon: , type: 'redis-db' as const, - dataRef: { ...conn, redisDB: db.index, redisKeyCount: keyCount }, + dataRef: { ...conn, redisDB: db.index, redisKeyCount: keyCount, redisDbAlias: alias }, isLeaf: true, dbIndex: db.index, }; diff --git a/frontend/src/utils/redisDbAlias.test.ts b/frontend/src/utils/redisDbAlias.test.ts index a8856db6..fd455699 100644 --- a/frontend/src/utils/redisDbAlias.test.ts +++ b/frontend/src/utils/redisDbAlias.test.ts @@ -68,13 +68,12 @@ describe('redisDbAlias helpers', () => { }); it('builds the sidebar label with and without an alias', () => { - expect(buildRedisDbNodeLabel(0, 'cache')).toBe('db0 (cache)'); + expect(buildRedisDbNodeLabel(0, 'cache')).toBe('db0 cache'); expect(buildRedisDbNodeLabel(3, '')).toBe('db3'); expect(buildRedisDbNodeLabel(0, ' ')).toBe('db0'); }); - it('appends the key-count suffix after the alias', () => { - expect(buildRedisDbNodeLabel(0, 'cache', ' (12)')).toBe('db0 (cache) (12)'); - expect(buildRedisDbNodeLabel(0, '', ' (12)')).toBe('db0 (12)'); + it('does not append key counts to the sidebar label', () => { + expect(buildRedisDbNodeLabel(0, '12')).toBe('db0 12'); }); }); diff --git a/frontend/src/utils/redisDbAlias.ts b/frontend/src/utils/redisDbAlias.ts index 69e84446..f8d13823 100644 --- a/frontend/src/utils/redisDbAlias.ts +++ b/frontend/src/utils/redisDbAlias.ts @@ -5,7 +5,7 @@ * user works with several connections that each use those indices for a * different purpose, the numbers are indistinguishable in the sidebar. An alias * map lets the user label, for example, `db0` as `cache` and have the sidebar - * render `db0 (cache)`. + * render `db0 cache`. * * The map is purely a client-side display preference and is keyed by * connection id so aliases stay independent across connections. The underlying @@ -128,17 +128,14 @@ export const setRedisDbAlias = ( /** * Build the sidebar label for a Redis DB node. Returns `dbN` when there is no - * alias, and `dbN (alias)` when one is set. `suffix` carries the existing - * key-count fragment (e.g. ` (12)`) and is always appended last so the alias - * stays adjacent to the index. + * alias, and `dbN alias` when one is set. Key counts are intentionally not + * included here: V2 renders them via the node meta slot to avoid duplicates. */ export const buildRedisDbNodeLabel = ( dbIndex: number, alias: string, - suffix = '', ): string => { const base = `db${dbIndex}`; const sanitizedAlias = sanitizeRedisDbAlias(alias); - const labelled = sanitizedAlias ? `${base} (${sanitizedAlias})` : base; - return `${labelled}${suffix}`; + return sanitizedAlias ? `${base} ${sanitizedAlias}` : base; }; diff --git a/frontend/src/v2-theme.css b/frontend/src/v2-theme.css index 9374a490..d91522fd 100644 --- a/frontend/src/v2-theme.css +++ b/frontend/src/v2-theme.css @@ -2841,6 +2841,20 @@ body[data-ui-version="v2"] .gn-v2-tree-label { font-weight: 400 !important; } +body[data-ui-version="v2"] .gn-v2-tree-title.is-redis-db .gn-v2-tree-label { + display: inline-flex; + align-items: baseline; + gap: 6px; +} + +body[data-ui-version="v2"] .gn-v2-redis-db-alias { + color: var(--gn-fg-5); + font-family: var(--gn-font-sans); + font-size: clamp(10px, calc(var(--gn-sidebar-tree-font-size, var(--gn-font-size-sm, 12px)) - 1px), 16px); + font-weight: 400 !important; + opacity: 0.78; +} + body[data-ui-version="v2"] .gn-v2-tree-connection-copy { min-width: max-content; flex: 0 0 auto;