feat(redis): delete leaf keys from the key list

This commit is contained in:
二丫讲梵 authored and GitHub committed 2026-08-21 12:20:57 +08:00
1 parent 25d5b885ba
commit b09fd35cd8
10 files changed
+58 -2

No files matched your search

@@ -400,11 +400,12 @@ function resetApiMocks() {
mocks.canBuildRedisFuzzyTree.mockImplementation((loadedKeyCount: number) => loadedKeyCount <= 200_000);
}
function mountBrowser() {
function mountBrowser(withDeleteDetails = false) {
const host = document.createElement("div");
document.body.append(host);
const app = createApp(RedisKeyBrowser, { connectionId: "connection", db: 0, blockDangerousRedisCommands: false });
app.use(createI18n({ legacy: false, locale: "en", messages: { en: {} }, missingWarn: false, fallbackWarn: false }));
const messages = { en: { redis: { deleteGroupDetails: withDeleteDetails ? "{target}\n{count} keys" : "redis.deleteGroupDetails" } } };
app.use(createI18n({ legacy: false, locale: "en", messages, missingWarn: false, fallbackWarn: false }));
app.mount(host);
mountedApps.push({ unmount: () => app.unmount(), host });
return host;
@@ -1126,6 +1127,29 @@ describe("RedisKeyBrowser expiry creation", () => {
});
describe("RedisKeyBrowser fuzzy key hierarchy", () => {
it("deletes a leaf directly from the key list after confirmation", async () => {
const key = { key_display: "session:current", key_raw: "c2Vzc2lvbjpjdXJyZW50", key_type: "string", ttl: -1 };
mocks.redisScanKeysBatch.mockResolvedValue({ cursor: 0, keys: [key], total_keys: 1 });
mocks.redisDeleteKeys.mockResolvedValue(1);
mountBrowser(true);
await settle();
groupRow("session").dispatchEvent(new MouseEvent("click", { bubbles: true, cancelable: true }));
await settle();
const deleteButton = requiredElement<HTMLButtonElement>('button[title="redis.deleteKey"]');
deleteButton.click();
await settle();
expect(mocks.redisDeleteKeys).not.toHaveBeenCalled();
expect(requiredElement<HTMLElement>("[data-test-danger-details]").textContent).toContain("session:current");
requiredElement<HTMLButtonElement>("[data-test-danger-confirm]").click();
await settle();
expect(mocks.redisDeleteKeys).toHaveBeenCalledWith("connection", 0, [key.key_raw]);
expect(document.body.textContent).not.toContain("session:current");
});
it("preserves the cursor and finds a sparse fuzzy match after a bounded continuation", async () => {
mocks.redisScanPageSize = 1_000;
mountBrowser();
@@ -1046,6 +1046,18 @@ function requestGroupDelete(node: RedisKeyTreeNode, event: Event) {
showDangerConfirm.value = true;
}
function requestKeyDelete(node: RedisKeyTreeNode, event: Event) {
event.stopPropagation();
if (node.kind !== "leaf" || selectionBusy.value) return;
pendingDanger.value = {
kind: "delete-keys",
title: node.fullKeyDisplay,
keyRaws: [node.keyRaw],
loadedSearchResults: false,
};
showDangerConfirm.value = true;
}
async function copyRedisKeyName(keyName: string) {
try {
await copyToClipboard(keyName);
@@ -2192,6 +2204,18 @@ defineExpose({ focusSearch, insertCommand, executeCommand: executeAiCommand });
<Button v-if="row.node.kind === 'group' && !isFuzzyHierarchyView" variant="ghost" size="icon" class="h-5 w-5 shrink-0 text-destructive opacity-0 group-hover:opacity-100" :title="t('redis.deleteGroup')" :disabled="selectionBusy" @click="requestGroupDelete(row.node, $event)">
<Trash2 class="h-3 w-3" />
</Button>
<Button
v-else-if="row.node.kind === 'leaf'"
variant="ghost"
size="icon"
class="h-5 w-5 shrink-0 text-destructive opacity-0 group-hover:opacity-100"
:title="t('redis.deleteKey')"
:aria-label="t('redis.deleteKey')"
:disabled="selectionBusy"
@click="requestKeyDelete(row.node, $event)"
>
<Trash2 class="h-3 w-3" />
</Button>
</div>
</div>
</CustomContextMenu>