diff --git a/pkg/services/dashboardversion/dashverimpl/dashver.go b/pkg/services/dashboardversion/dashverimpl/dashver.go index 2a4d31f2b4d..8f9e01bbeea 100644 --- a/pkg/services/dashboardversion/dashverimpl/dashver.go +++ b/pkg/services/dashboardversion/dashverimpl/dashver.go @@ -274,6 +274,11 @@ func (s *Service) listDashboardVersionsThroughK8s( continueToken = tempOut.GetContinue() } + // Update the continue token on the response to reflect the actual position after all fetched items. + // Without this, the response would return the token from the first fetch, causing duplicate items + // on subsequent pages when multiple fetches were needed to fill the requested limit. + out.SetContinue(continueToken) + return out, nil } diff --git a/pkg/services/dashboardversion/dashverimpl/dashver_test.go b/pkg/services/dashboardversion/dashverimpl/dashver_test.go index 909e98ee33c..18e3c295ab5 100644 --- a/pkg/services/dashboardversion/dashverimpl/dashver_test.go +++ b/pkg/services/dashboardversion/dashverimpl/dashver_test.go @@ -268,6 +268,58 @@ func TestListDashboardVersions(t *testing.T) { }}}, res) }) + t.Run("List returns continue token when first fetch satisfies limit with more 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: 2} + 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") // More pages exist + + mockCli.On("List", mock.Anything, mock.Anything, mock.Anything).Return(firstPage, nil).Once() + + res, err := dashboardVersionService.List(context.Background(), &query) + require.Nil(t, err) + require.Equal(t, 2, len(res.Versions)) + require.Equal(t, "t1", res.ContinueToken) // Token from first fetch when limit is satisfied + mockCli.AssertNumberOfCalls(t, "List", 1) // Only one fetch needed + }) + 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()} @@ -333,7 +385,79 @@ func TestListDashboardVersions(t *testing.T) { res, err := dashboardVersionService.List(context.Background(), &query) require.Nil(t, err) require.Equal(t, 3, len(res.Versions)) - require.Equal(t, "t1", res.ContinueToken) // Implementation returns continue token from first page + require.Equal(t, "", res.ContinueToken) // Should return token from last fetch (empty = no more pages) + mockCli.AssertNumberOfCalls(t, "List", 2) + }) + + t.Run("List returns continue token from last fetch when more pages exist", 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{}, + }}, + }, + } + secondMeta, err := meta.ListAccessor(secondPage) + require.NoError(t, err) + secondMeta.SetContinue("t2") // More pages exist + + 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, "t2", res.ContinueToken) // Must return token from LAST fetch, not first mockCli.AssertNumberOfCalls(t, "List", 2) })