From 3dceaac5dc24c3b2b2f7c8eb0c12b3d38808792c Mon Sep 17 00:00:00 2001 From: Will Assis <35489495+gassiss@users.noreply.github.com> Date: Wed, 15 Oct 2025 16:32:25 -0400 Subject: [PATCH] fix pagination not working in legacy modes with kubernetes apis enabled (#112478) * dashboards: fix pagination not working in legacy modes with kubernetes apis enabled --- pkg/registry/apis/dashboard/search.go | 4 + pkg/registry/apis/dashboard/search_test.go | 92 ++++++++++++++++++++++ 2 files changed, 96 insertions(+) diff --git a/pkg/registry/apis/dashboard/search.go b/pkg/registry/apis/dashboard/search.go index 5b0e93a0444..7dba6e1f8f1 100644 --- a/pkg/registry/apis/dashboard/search.go +++ b/pkg/registry/apis/dashboard/search.go @@ -279,8 +279,12 @@ func (s *SearchHandler) DoSearch(w http.ResponseWriter, r *http.Request) { } if queryParams.Has("offset") { offset, _ = strconv.Atoi(queryParams.Get("offset")) + if offset > 0 { + page = (offset / limit) + 1 + } } else if queryParams.Has("page") { page, _ = strconv.Atoi(queryParams.Get("page")) + offset = (page - 1) * limit } searchRequest := &resourcepb.ResourceSearchRequest{ diff --git a/pkg/registry/apis/dashboard/search_test.go b/pkg/registry/apis/dashboard/search_test.go index 4224d5927b3..b392cb1606c 100644 --- a/pkg/registry/apis/dashboard/search_test.go +++ b/pkg/registry/apis/dashboard/search_test.go @@ -189,6 +189,98 @@ func TestSearchFallback(t *testing.T) { }) } +func TestSearchHandlerPagination(t *testing.T) { + t.Run("should calculate offset and page parameters", func(t *testing.T) { + limit := 50 + for i, tt := range []struct { + offset int + page int + expectedOffset int + expectedPage int + }{ + { + offset: 0, + page: 0, + expectedOffset: 0, + expectedPage: 1, + }, + { + offset: 0, + page: 1, + expectedOffset: 0, + expectedPage: 1, + }, + { + offset: 0, + page: 2, + expectedOffset: 50, + expectedPage: 2, + }, + { + offset: 0, + page: 3, + expectedOffset: 100, + expectedPage: 3, + }, + { + offset: 50, + page: 0, + expectedOffset: 50, + expectedPage: 2, + }, + { + offset: 100, + page: 0, + expectedOffset: 100, + expectedPage: 3, + }, + { + offset: 149, + page: 0, + expectedOffset: 149, + expectedPage: 3, + }, + { + offset: 150, + page: 0, + expectedOffset: 150, + expectedPage: 4, + }, + } { + mockClient := &MockClient{} + + cfg := &setting.Cfg{ + UnifiedStorage: map[string]setting.UnifiedStorageConfig{ + "dashboards.dashboard.grafana.app": {DualWriterMode: rest.Mode0}, + }, + } + dual := dualwrite.ProvideStaticServiceForTests(cfg) + searchHandler := NewSearchHandler(tracing.NewNoopTracerService(), dual, mockClient, mockClient, nil) + + rr := httptest.NewRecorder() + endpoint := fmt.Sprintf("/search?limit=%d", limit) + if tt.offset > 0 { + endpoint = fmt.Sprintf("%s&offset=%d", endpoint, tt.offset) + } + if tt.page > 0 { + endpoint = fmt.Sprintf("%s&page=%d", endpoint, tt.page) + } + req := httptest.NewRequest("GET", endpoint, nil) + req.Header.Add("content-type", "application/json") + req = req.WithContext(identity.WithRequester(req.Context(), &user.SignedInUser{Namespace: "test"})) + + searchHandler.DoSearch(rr, req) + + if mockClient.LastSearchRequest == nil { + t.Fatalf("expected Search to be called, but it was not") + } + + require.Equal(t, int(mockClient.LastSearchRequest.Offset), tt.expectedOffset, fmt.Sprintf("mismatch offset in test %d", i)) + require.Equal(t, int(mockClient.LastSearchRequest.Page), tt.expectedPage, fmt.Sprintf("mismatch page in test %d", i)) + } + }) +} + func TestSearchHandler(t *testing.T) { t.Run("Multiple comma separated fields will be appended to default dashboard search fields", func(t *testing.T) { // Create a mock client