From cc1f00cbfbbc77c17b3251101e95248815b12e37 Mon Sep 17 00:00:00 2001 From: Stephanie Hingtgen Date: Mon, 11 Aug 2025 07:12:16 -0600 Subject: [PATCH] Dashboard versions: Fix list for large dashboards (#109433) --- .../dashboardversion/dashverimpl/dashver.go | 18 ++++- .../dashverimpl/dashver_test.go | 67 +++++++++++++++++++ 2 files changed, 84 insertions(+), 1 deletion(-) diff --git a/pkg/services/dashboardversion/dashverimpl/dashver.go b/pkg/services/dashboardversion/dashverimpl/dashver.go index b2063f44091..77a4d0576fd 100644 --- a/pkg/services/dashboardversion/dashverimpl/dashver.go +++ b/pkg/services/dashboardversion/dashverimpl/dashver.go @@ -259,13 +259,29 @@ func (s *Service) listHistoryThroughK8s(ctx context.Context, orgID int64, dashbo return nil, dashboards.ErrDashboardNotFound } + // if k8s returns a continue token, we need to fetch the next page(s) until we either reach the limit or there are no more pages + continueToken = out.GetContinue() + for (len(out.Items) < int(limit)) && (continueToken != "") { + tempOut, err := s.k8sclient.List(ctx, orgID, v1.ListOptions{ + LabelSelector: labelSelector, + FieldSelector: fieldSelector, + Continue: continueToken, + Limit: limit - int64(len(out.Items)), + }) + if err != nil { + return nil, err + } + out.Items = append(out.Items, tempOut.Items...) + continueToken = tempOut.GetContinue() + } + dashboards, err := s.UnstructuredToLegacyDashboardVersionList(ctx, out.Items, orgID) if err != nil { return nil, err } return &dashver.DashboardVersionResponse{ - ContinueToken: out.GetContinue(), + ContinueToken: continueToken, Versions: dashboards, }, nil } diff --git a/pkg/services/dashboardversion/dashverimpl/dashver_test.go b/pkg/services/dashboardversion/dashverimpl/dashver_test.go index 1b32379e1a5..0bca02e8e93 100644 --- a/pkg/services/dashboardversion/dashverimpl/dashver_test.go +++ b/pkg/services/dashboardversion/dashverimpl/dashver_test.go @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/meta" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime/schema" @@ -197,6 +198,72 @@ func TestListDashboardVersions(t *testing.T) { }}}, res) }) + t.Run("List returns correct continue token across multiple pages", func(t *testing.T) { + dashboardService := dashboards.NewFakeDashboardService(t) + dashboardVersionService := Service{dashSvc: dashboardService, features: featuremgmt.WithFeatures()} + mockCli := new(client.MockK8sHandler) + dashboardVersionService.k8sclient = mockCli + dashboardVersionService.features = featuremgmt.WithFeatures() + + dashboardService.On("GetDashboardUIDByID", mock.Anything, + mock.AnythingOfType("*dashboards.GetDashboardRefByIDQuery")). + Return(&dashboards.DashboardRef{UID: "uid"}, nil) + query := dashver.ListDashboardVersionsQuery{DashboardID: 42, Limit: 3} + mockCli.On("GetUsersFromMeta", mock.Anything, mock.Anything).Return(map[string]*user.User{}, nil) + firstPage := &unstructured.UnstructuredList{ + Items: []unstructured.Unstructured{ + {Object: map[string]any{ + "metadata": map[string]any{ + "name": "uid", + "resourceVersion": "11", + "generation": int64(4), + "labels": map[string]any{ + utils.LabelKeyDeprecatedInternalID: "42", // nolint:staticcheck + }, + }, + "spec": map[string]any{}, + }}, + {Object: map[string]any{ + "metadata": map[string]any{ + "name": "uid", + "resourceVersion": "12", + "generation": int64(5), + "labels": map[string]any{ + utils.LabelKeyDeprecatedInternalID: "42", // nolint:staticcheck + }, + }, + "spec": map[string]any{}, + }}, + }, + } + firstMeta, err := meta.ListAccessor(firstPage) + require.NoError(t, err) + firstMeta.SetContinue("t1") + secondPage := &unstructured.UnstructuredList{ + Items: []unstructured.Unstructured{ + {Object: map[string]any{ + "metadata": map[string]any{ + "name": "uid", + "resourceVersion": "13", + "generation": int64(6), + "labels": map[string]any{ + utils.LabelKeyDeprecatedInternalID: "42", // nolint:staticcheck + }, + }, + "spec": map[string]any{}, + }}, + }, + } + mockCli.On("List", mock.Anything, mock.Anything, mock.Anything).Return(firstPage, nil).Once() + mockCli.On("List", mock.Anything, mock.Anything, mock.Anything).Return(secondPage, nil).Once() + + res, err := dashboardVersionService.List(context.Background(), &query) + require.Nil(t, err) + require.Equal(t, 3, len(res.Versions)) + require.Equal(t, "", res.ContinueToken) + mockCli.AssertNumberOfCalls(t, "List", 2) + }) + t.Run("should return dashboard not found error when k8s client says not found", func(t *testing.T) { dashboardService := dashboards.NewFakeDashboardService(t) dashboardVersionService := Service{dashSvc: dashboardService, features: featuremgmt.WithFeatures()}