diff --git a/apps/desktop/src/components/redis/RedisKeyBrowser.vue b/apps/desktop/src/components/redis/RedisKeyBrowser.vue index e452aea292..4b09da26ea 100644 --- a/apps/desktop/src/components/redis/RedisKeyBrowser.vue +++ b/apps/desktop/src/components/redis/RedisKeyBrowser.vue @@ -22,7 +22,7 @@ import * as api from "@/lib/api"; import type { RedisKeyInfo, RedisScanResult, RedisValue, HistoryEntry } from "@/lib/api"; import { uuid } from "@/lib/utils"; import { useConnectionStore } from "@/stores/connectionStore"; -import { buildRedisKeyTree, collectExpandedGroupIds, collectRedisGroupKeyRaws, flattenVisibleRedisKeyTree, mergeKeysIntoRedisKeyTree, redisKeyToFlatTreeRow, type RedisKeyTreeNode } from "@/lib/redisKeyTree"; +import { buildRedisKeyTree, collectExpandedGroupIds, collectRedisGroupKeyRaws, flattenVisibleRedisKeyTree, mergeKeysIntoRedisKeyTree, redisKeyNameCopyText, redisKeyToFlatTreeRow, type RedisKeyTreeNode } from "@/lib/redisKeyTree"; import { classifyRedisCommandSafety } from "@/lib/redisCommandSafety"; import { isRedisMutatingCommand } from "@/lib/redisCommandTable"; import { isRedisClearScreenCommand, nextRedisCommandDb, redisKeyTextToRaw } from "@/lib/redisCommandSession"; @@ -427,9 +427,9 @@ function requestGroupDelete(node: RedisKeyTreeNode, event: Event) { showDangerConfirm.value = true; } -async function copyRedisKeyName(keyRaw: string) { +async function copyRedisKeyName(keyName: string) { try { - await copyToClipboard(keyRaw); + await copyToClipboard(keyName); toast(t("redis.copied"), 2000); } catch (e: any) { toast(t("grid.copyFailed", { message: e?.message || String(e) }), 5000); @@ -437,12 +437,13 @@ async function copyRedisKeyName(keyRaw: string) { } function redisKeyContextMenuItems(node: RedisKeyTreeNode): ContextMenuItem[] { - if (node.kind !== "leaf") return []; + const copyText = redisKeyNameCopyText(node); + if (copyText === null) return []; return [ { label: t("redis.copyKeyName"), icon: Copy, - action: () => copyRedisKeyName(node.keyRaw), + action: () => copyRedisKeyName(copyText), }, ]; } diff --git a/apps/desktop/src/lib/redisKeyTree.ts b/apps/desktop/src/lib/redisKeyTree.ts index c76a0f78cc..fcda732ee6 100644 --- a/apps/desktop/src/lib/redisKeyTree.ts +++ b/apps/desktop/src/lib/redisKeyTree.ts @@ -29,6 +29,12 @@ export interface RedisKeyTreeRow { depth: number; } +export function redisKeyNameCopyText(node: RedisKeyTreeNode): string | null { + // keyRaw is base64-encoded for backend roundtrips; copy the user-visible + // Redis key name instead of the internal transport value. + return node.kind === "leaf" ? node.fullKeyDisplay : null; +} + function buildGroupId(db: number, pathSegments: string[]): string { return `group:${db}:${pathSegments.join("\u0000")}`; } diff --git a/packages/app-tests/redisKeyTree.test.ts b/packages/app-tests/redisKeyTree.test.ts index 5d77e54d01..dc80bd5cdd 100644 --- a/packages/app-tests/redisKeyTree.test.ts +++ b/packages/app-tests/redisKeyTree.test.ts @@ -1,6 +1,6 @@ import { test } from "vitest"; import assert from "node:assert/strict"; -import { buildRedisKeyTree, collectRedisGroupKeyRaws, collectExpandedGroupIds, flattenVisibleRedisKeyTree, mergeKeysIntoRedisKeyTree, redisKeyToFlatTreeRow, type RedisKeyTreeNode } from "../../apps/desktop/src/lib/redisKeyTree.ts"; +import { buildRedisKeyTree, collectRedisGroupKeyRaws, collectExpandedGroupIds, flattenVisibleRedisKeyTree, mergeKeysIntoRedisKeyTree, redisKeyNameCopyText, redisKeyToFlatTreeRow, type RedisKeyTreeNode } from "../../apps/desktop/src/lib/redisKeyTree.ts"; import type { RedisKeyInfo } from "../../apps/desktop/src/lib/api.ts"; function makeKey(key_display: string, key_raw: string, key_type = "string", ttl = -1): RedisKeyInfo { @@ -96,6 +96,24 @@ test("redisKeyToFlatTreeRow keeps search results flat with the full key label", assert.equal(row.node.keyType, ""); }); +test("redisKeyNameCopyText uses the display name instead of raw encoding", () => { + const tree = buildRedisKeyTree([makeKey("user:profile:1", "dXNlcjpwcm9maWxlOjE=")], 0); + const userGroup = tree[0]; + assert.equal(userGroup?.kind, "group"); + if (!userGroup || userGroup.kind !== "group") return; + + assert.equal(redisKeyNameCopyText(userGroup), null); + const profileGroup = userGroup.children[0]; + assert.equal(profileGroup?.kind, "group"); + if (!profileGroup || profileGroup.kind !== "group") return; + + const leaf = profileGroup.children[0]; + assert.equal(leaf?.kind, "leaf"); + if (!leaf || leaf.kind !== "leaf") return; + assert.equal(redisKeyNameCopyText(leaf), "user:profile:1"); + assert.notEqual(redisKeyNameCopyText(leaf), leaf.keyRaw); +}); + test("collectRedisGroupKeyRaws returns every leaf key under a group", () => { const tree = buildRedisKeyTree([makeKey("user:profile:name", "k1"), makeKey("user:profile:email", "k2"), makeKey("user:settings", "k3"), makeKey("session:1", "k4")], 0);