diff --git a/apps/desktop/src/components/redis/RedisValueViewer.collectionSearch.spec.ts b/apps/desktop/src/components/redis/RedisValueViewer.collectionSearch.spec.ts index 663852ff87..2fa5a494eb 100644 --- a/apps/desktop/src/components/redis/RedisValueViewer.collectionSearch.spec.ts +++ b/apps/desktop/src/components/redis/RedisValueViewer.collectionSearch.spec.ts @@ -654,4 +654,54 @@ describe("Redis collection member search", () => { expect(document.body.textContent).toContain("1 members"); expect(document.body.textContent).not.toContain("/ 100 members loaded"); }); + + it("loads more hash fields when clicking load more in hash viewer", async () => { + mocks.redisGetValue.mockResolvedValue({ + key_display: "key", + key_raw: "key", + ttl: -1, + redis_type: "hash", + data: { + kind: "hash", + items: [{ field: blob("f1"), value: blob("v1") }], + total: 4000, + scan_cursor: 1234, + }, + }); + mocks.redisLoadMore.mockResolvedValueOnce({ + kind: "hash", + items: [{ field: blob("f2"), value: blob("v2") }], + scan_cursor: 5678, + }); + mountViewer(); + await settle(); + expect(document.body.textContent).toContain("f1"); + expect(document.body.textContent).not.toContain("f2"); + loadMoreButton().click(); + await settle(); + expect(mocks.redisLoadMore).toHaveBeenCalledWith("connection", 0, "key", "hash", 1234, 200, undefined, undefined); + expect(document.body.textContent).toContain("f1"); + expect(document.body.textContent).toContain("f2"); + }); + + it("shows error toast when ordinary hash load more fails", async () => { + mocks.redisGetValue.mockResolvedValue({ + key_display: "key", + key_raw: "key", + ttl: -1, + redis_type: "hash", + data: { + kind: "hash", + items: [{ field: blob("f1"), value: blob("v1") }], + total: 4000, + scan_cursor: 1234, + }, + }); + mocks.redisLoadMore.mockRejectedValueOnce(new Error("Collection cursor is invalid, expired, or belongs to a different connection/database")); + mountViewer(); + await settle(); + loadMoreButton().click(); + await settle(); + expect(mocks.toast).toHaveBeenCalledWith("Collection cursor is invalid, expired, or belongs to a different connection/database", 3000); + }); }); diff --git a/apps/desktop/src/components/redis/RedisValueViewer.vue b/apps/desktop/src/components/redis/RedisValueViewer.vue index c2bd9497cc..71ad427373 100644 --- a/apps/desktop/src/components/redis/RedisValueViewer.vue +++ b/apps/desktop/src/components/redis/RedisValueViewer.vue @@ -1898,8 +1898,14 @@ async function loadMore() { if (!collectionSearchSupported.value) return; const scope = collectionScope(); if (!scope) return; - if (activeCollectionSearchQuery.value) await collectionSearchController.start(); - else await collectionSearchController.browse(scope, collectionItems.value, scanCursor.value!); + if (activeCollectionSearchQuery.value) { + await collectionSearchController.start(); + } else { + await collectionSearchController.browse(scope, collectionItems.value, scanCursor.value!); + if (collectionSearchState.value.status === "failed" && collectionSearchState.value.error) { + toast(errorMessage(collectionSearchState.value.error), 3000); + } + } } async function saveString() { diff --git a/apps/desktop/src/lib/redis/redisCollectionSearchController.ts b/apps/desktop/src/lib/redis/redisCollectionSearchController.ts index 282a076acf..40af5fd64a 100644 --- a/apps/desktop/src/lib/redis/redisCollectionSearchController.ts +++ b/apps/desktop/src/lib/redis/redisCollectionSearchController.ts @@ -113,8 +113,13 @@ export class RedisCollectionSearchController { browse(scope: RedisCollectionScope, items: RedisCollectionItem[], cursor: number) { const current = this.session; const sameScope = current && this.sameScope(current.scope, scope); - if (!sameScope || current.state.query || current.state.cursor !== cursor) this.prepare(scope, ""); - this.session!.state = { status: "searching", query: "", items, cursor }; + if (!sameScope || current.state.query || current.state.cursor !== cursor) { + this.invalidate(); + this.browsingSession = undefined; + this.session = { scope: { ...scope }, state: { status: "searching", query: "", items, cursor } }; + } else { + this.session!.state = { ...this.session!.state, status: "searching", query: "", items, cursor }; + } return this.start(); } diff --git a/crates/dbx-core/src/query/redis_ops.rs b/crates/dbx-core/src/query/redis_ops.rs index 8662769920..ccc6e2e855 100644 --- a/crates/dbx-core/src/query/redis_ops.rs +++ b/crates/dbx-core/src/query/redis_ops.rs @@ -153,28 +153,18 @@ pub async fn redis_get_value_in_db_with_preview_limit_core( match &pool { PoolKind::Redis(redis) => { let key = redis_driver::redis_key_raw_to_bytes(key_raw)?; + let scope = redis_driver::RedisOperationScope { connection_id: connection_id.to_string(), db }; match redis.as_ref() { RedisConnection::Direct(con) => { let mut con = con.lock().await; redis_driver::select_db(&mut *con, db).await?; - redis_driver::get_value_scoped_with_preview_limit( - &mut *con, - &key, - &redis_driver::RedisOperationScope { connection_id: connection_id.to_string(), db }, - preview_limit_bytes, - ) - .await + redis_driver::get_value_scoped_with_preview_limit(&mut *con, &key, &scope, preview_limit_bytes) + .await } RedisConnection::Cluster(cluster) => { redis_driver::ensure_cluster_database(cluster, db).await?; let mut con = redis_driver::cluster_key_connection_in_db(cluster, &key, db).await?; - redis_driver::get_value_scoped_with_preview_limit( - &mut con, - &key, - &redis_driver::RedisOperationScope { connection_id: connection_id.to_string(), db }, - preview_limit_bytes, - ) - .await + redis_driver::get_value_scoped_with_preview_limit(&mut con, &key, &scope, preview_limit_bytes).await } } } @@ -1300,12 +1290,22 @@ pub async fn redis_load_more_in_db_core( match &pool { PoolKind::Redis(redis) => { let key = redis_driver::redis_key_raw_to_bytes(key_raw)?; + let scope = redis_driver::RedisOperationScope { connection_id: connection_id.to_string(), db }; match redis.as_ref() { RedisConnection::Direct(con) => { let mut con = con.lock().await; redis_driver::select_db(&mut *con, db).await?; - redis_driver::load_more_collection(&mut *con, &key, key_type, cursor, count, filter, sort_direction) - .await + redis_driver::load_more_collection_scoped( + &mut *con, + &key, + key_type, + cursor, + count, + filter, + sort_direction, + &scope, + ) + .await } RedisConnection::Cluster(cluster) => { redis_driver::ensure_cluster_database(cluster, db).await?; @@ -1318,7 +1318,7 @@ pub async fn redis_load_more_in_db_core( count, filter, sort_direction, - &redis_driver::RedisOperationScope { connection_id: connection_id.to_string(), db }, + &scope, ) .await }