fix(redis): mark key-tree counts as partial while scanning

This commit is contained in:
Melody Ruch authored and GitHub committed 2026-08-18 22:01:55 +08:00
1 parent 3c1813123d
commit 7be64f45a7
10 files changed
+48 -1

No files matched your search

@@ -574,3 +574,40 @@ describe("RedisKeyBrowser loadMore failure handling (PR #6313 review)", () => {
expect(mocks.redisScanKeysBatch.mock.calls.length).toBeLessThanOrEqual(1 + AUTO_LOAD_MAX_CALLS);
});
});
// A group's `loadedLeafCount` only reflects keys the SCAN happened to return
// before auto-load stopped (see `redisKeyTree.ts`); when the cursor hasn't
// reached 0 yet, that count is not the folder's real total. Issue #6392: a
// user compared DBX's tree against `redis-cli --scan` and saw folder counts
// far below the real per-prefix cardinality, with nothing in the UI hinting
// the numbers were partial.
describe("RedisKeyBrowser group key counts reflect incomplete loading (issue #6392)", () => {
it("marks a group's count as partial while more keys remain unscanned", async () => {
stubNonOverflowingViewport();
// cursor never returns to 0, so hasMore stays true even after the
// shared auto-load budget is exhausted.
mocks.redisScanKeysBatch.mockImplementation((_connectionId: string, _db: number, cursor: number) => {
if (cursor === 0) return Promise.resolve({ cursor: 1, keys: [keyInfo("grp:a"), keyInfo("grp:b")], total_keys: 500 });
return Promise.resolve({ cursor: cursor + 1, keys: [], total_keys: 0 });
});
const host = mountBrowser();
await settleThoroughly();
const countBadge = host.querySelector(".text-muted-foreground.ml-1");
expect(countBadge?.textContent).toBe("(2+)");
expect(countBadge?.getAttribute("title")).toBeTruthy();
});
it("shows an exact count once every key has actually been scanned", async () => {
stubNonOverflowingViewport();
mocks.redisScanKeysBatch.mockResolvedValue({ cursor: 0, keys: [keyInfo("grp:a"), keyInfo("grp:b")], total_keys: 2 });
const host = mountBrowser();
await settleThoroughly();
const countBadge = host.querySelector(".text-muted-foreground.ml-1");
expect(countBadge?.textContent).toBe("(2)");
expect(countBadge?.getAttribute("title")).toBeFalsy();
});
});
@@ -2045,7 +2045,9 @@ defineExpose({ focusSearch, insertCommand, executeCommand: executeAiCommand });
<component :is="expandedGroupIds.has(row.node.id) ? ChevronDown : ChevronRight" class="w-3 h-3 shrink-0 text-muted-foreground" />
<component :is="expandedGroupIds.has(row.node.id) ? FolderOpen : FolderClosed" class="h-3.5 w-3.5 shrink-0 text-amber-500" />
<span class="dbx-editor-font-family truncate">{{ row.node.label }}</span>
<span class="text-muted-foreground ml-1" :title="isFuzzyHierarchyView ? t('redis.loadedMatchingKeys', { count: row.node.loadedLeafCount }) : undefined">({{ row.node.loadedLeafCount }})</span>
<span class="text-muted-foreground ml-1" :title="isFuzzyHierarchyView ? t('redis.loadedMatchingKeys', { count: row.node.loadedLeafCount }) : hasMore ? t('redis.loadedGroupKeysPartial', { count: row.node.loadedLeafCount }) : undefined"
>({{ row.node.loadedLeafCount }}{{ !isFuzzyHierarchyView && hasMore ? "+" : "" }})</span
>
</template>
<template v-else>
<span class="relative flex h-4 w-4 shrink-0 items-center justify-center">
+1
View File
@@ -4361,6 +4361,7 @@ export default {
deselectAll: "Clear",
selectLoadedGroupKeys: "Select {count} loaded keys in this folder",
loadedMatchingKeys: "{count} loaded matching keys",
loadedGroupKeysPartial: "{count} keys loaded so far — this folder may contain more",
fuzzyTreeLimit: "{count} fuzzy matches are shown as a flat list to keep the browser responsive. Refine the search to view the hierarchy.",
deleteGroup: "Delete group",
deleteGroupDetails: "{target}\n{count} keys",
+1
View File
@@ -4207,6 +4207,7 @@ export default withEnglishFallback({
deselectAll: "Borrar",
selectLoadedGroupKeys: "Seleccionar {count} claves coincidentes cargadas",
loadedMatchingKeys: "{count} claves coincidentes cargadas",
loadedGroupKeysPartial: "{count} claves cargadas hasta ahora — esta carpeta puede contener más",
fuzzyTreeLimit: "Se muestran {count} coincidencias difusas como lista plana para mantener el navegador responsivo. Refina la búsqueda para ver la jerarquía.",
deleteGroup: "Eliminar grupo",
deleteGroupDetails: "{target}\n{count} claves",
+1
View File
@@ -4205,6 +4205,7 @@ export default withEnglishFallback({
deselectAll: "Cancella",
selectLoadedGroupKeys: "Seleziona {count} chiavi corrispondenti caricate",
loadedMatchingKeys: "{count} chiavi corrispondenti caricate",
loadedGroupKeysPartial: "{count} chiavi caricate finora — questa cartella potrebbe contenerne altre",
fuzzyTreeLimit: "{count} corrispondenze fuzzy sono mostrate come elenco piatto per mantenere il browser reattivo. Restringi la ricerca per vedere la gerarchia.",
deleteGroup: "Elimina gruppo",
deleteGroupDetails: "{target}\n{count} chiavi",
+1
View File
@@ -4235,6 +4235,7 @@ export default withEnglishFallback({
deselectAll: "クリア",
selectLoadedGroupKeys: "読み込み済みの一致キー {count} 件を選択",
loadedMatchingKeys: "読み込み済みの一致キー {count} 件",
loadedGroupKeysPartial: "これまでに読み込まれたキー {count} 件 — このフォルダーにはさらにキーが存在する可能性があります",
fuzzyTreeLimit: "ブラウザーの応答性を保つため、あいまい一致 {count} 件をフラットな一覧で表示しています。階層を表示するには検索条件を絞り込んでください。",
deleteGroup: "グループを削除",
deleteGroupDetails: "{target}\n{count}個のキー",
+1
View File
@@ -3845,6 +3845,7 @@ export default withEnglishFallback({
deselectAll: "지우기",
selectLoadedGroupKeys: "로드된 {count}개 일치 키 선택",
loadedMatchingKeys: "로드된 일치 키 {count}개",
loadedGroupKeysPartial: "지금까지 로드된 {count}개 키 — 이 폴더에 더 많은 키가 있을 수 있습니다",
fuzzyTreeLimit: "{count}개의 퍼지 일치가 브라우저 응답성을 유지하기 위해 평면 목록으로 표시됩니다. 계층 구조를 보려면 검색을 좁히세요.",
deleteGroup: "그룹 삭제",
deleteGroupDetails: "{target}\n키 {count}개",
+1
View File
@@ -4207,6 +4207,7 @@ export default withEnglishFallback({
deselectAll: "Limpar",
selectLoadedGroupKeys: "Selecionar {count} chaves correspondentes carregadas",
loadedMatchingKeys: "{count} chaves correspondentes carregadas",
loadedGroupKeysPartial: "{count} chaves carregadas até agora — esta pasta pode conter mais",
fuzzyTreeLimit: "{count} correspondências difusas são mostradas como uma lista plana para manter o navegador responsivo. Refine a busca para ver a hierarquia.",
deleteGroup: "Excluir grupo",
deleteGroupDetails: "{target}\n{count} chaves",
+1
View File
@@ -4348,6 +4348,7 @@ export default withEnglishFallback({
deselectAll: "清除",
selectLoadedGroupKeys: "选择该文件夹下已加载的 {count} 个 key",
loadedMatchingKeys: "已加载的 {count} 个匹配 key",
loadedGroupKeysPartial: "目前已加载 {count} 个 key —— 该文件夹下可能还有更多",
fuzzyTreeLimit: "已加载 {count} 个模糊匹配 key。为保持浏览器响应,当前以扁平列表展示;请缩小搜索范围以查看层级。",
deleteGroup: "删除分组",
deleteGroupDetails: "{target}\n{count} 个 key",
+1
View File
@@ -3534,6 +3534,7 @@ export default withEnglishFallback({
deselectAll: "清除",
selectLoadedGroupKeys: "選取已載入的 {count} 個相符 key",
loadedMatchingKeys: "已載入的 {count} 個相符 key",
loadedGroupKeysPartial: "目前已載入 {count} 個 key —— 此資料夾下可能還有更多",
fuzzyTreeLimit: "已載入 {count} 個模糊相符 key。為保持瀏覽器回應,目前以扁平清單顯示;請縮小搜尋範圍以查看層級。",
deleteGroup: "刪除群組",
deleteGroupDetails: "{target}\n{count} 個 key",