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">