diff --git a/pkg/services/cloudmigration/api/api.go b/pkg/services/cloudmigration/api/api.go index b647433e114..1cd851a9717 100644 --- a/pkg/services/cloudmigration/api/api.go +++ b/pkg/services/cloudmigration/api/api.go @@ -392,6 +392,7 @@ func (cma *CloudMigrationAPI) GetSnapshotList(c *contextmodel.ReqContext) respon SessionUID: uid, Limit: c.QueryInt("limit"), Page: c.QueryInt("page"), + Sort: c.Query("sort"), } if q.Limit == 0 { q.Limit = 100 diff --git a/pkg/services/cloudmigration/api/api_test.go b/pkg/services/cloudmigration/api/api_test.go index 5926276a5d0..1b56c8a4c20 100644 --- a/pkg/services/cloudmigration/api/api_test.go +++ b/pkg/services/cloudmigration/api/api_test.go @@ -395,7 +395,23 @@ func TestCloudMigrationAPI_GetSnapshotList(t *testing.T) { requestUrl: "/api/cloudmigration/migration/1234/snapshots", basicRole: org.RoleAdmin, expectedHttpResult: http.StatusOK, - expectedBody: `{"snapshots":[{"uid":"fake_uid","status":"CREATING","sessionUid":"1234","created":"0001-01-01T00:00:00Z","finished":"0001-01-01T00:00:00Z"},{"uid":"fake_uid","status":"CREATING","sessionUid":"1234","created":"0001-01-01T00:00:00Z","finished":"0001-01-01T00:00:00Z"}]}`, + expectedBody: `{"snapshots":[{"uid":"fake_uid","status":"CREATING","sessionUid":"1234","created":"2024-06-05T17:30:40Z","finished":"0001-01-01T00:00:00Z"},{"uid":"fake_uid","status":"CREATING","sessionUid":"1234","created":"2024-06-05T18:30:40Z","finished":"0001-01-01T00:00:00Z"}]}`, + }, + { + desc: "with limit query param should return 200 if everything is ok", + requestHttpMethod: http.MethodGet, + requestUrl: "/api/cloudmigration/migration/1234/snapshots?limit=1", + basicRole: org.RoleAdmin, + expectedHttpResult: http.StatusOK, + expectedBody: `{"snapshots":[{"uid":"fake_uid","status":"CREATING","sessionUid":"1234","created":"2024-06-05T17:30:40Z","finished":"0001-01-01T00:00:00Z"}]}`, + }, + { + desc: "with sort query param should return 200 if everything is ok", + requestHttpMethod: http.MethodGet, + requestUrl: "/api/cloudmigration/migration/1234/snapshots?sort=latest", + basicRole: org.RoleAdmin, + expectedHttpResult: http.StatusOK, + expectedBody: `{"snapshots":[{"uid":"fake_uid","status":"CREATING","sessionUid":"1234","created":"2024-06-05T18:30:40Z","finished":"0001-01-01T00:00:00Z"},{"uid":"fake_uid","status":"CREATING","sessionUid":"1234","created":"2024-06-05T17:30:40Z","finished":"0001-01-01T00:00:00Z"}]}`, }, { desc: "should return 403 if no used is not admin", diff --git a/pkg/services/cloudmigration/cloudmigrationimpl/fake/cloudmigration_fake.go b/pkg/services/cloudmigration/cloudmigrationimpl/fake/cloudmigration_fake.go index 250c837b14e..50ccfff8ef9 100644 --- a/pkg/services/cloudmigration/cloudmigrationimpl/fake/cloudmigration_fake.go +++ b/pkg/services/cloudmigration/cloudmigrationimpl/fake/cloudmigration_fake.go @@ -3,6 +3,7 @@ package fake import ( "context" "fmt" + "sort" "time" "github.com/grafana/grafana/pkg/services/cloudmigration" @@ -108,18 +109,31 @@ func (m FakeServiceImpl) GetSnapshotList(ctx context.Context, query cloudmigrati if m.ReturnError { return nil, fmt.Errorf("mock error") } - return []cloudmigration.CloudMigrationSnapshot{ + + cloudSnapshots := []cloudmigration.CloudMigrationSnapshot{ { UID: "fake_uid", SessionUID: query.SessionUID, Status: cloudmigration.SnapshotStatusCreating, + Created: time.Date(2024, 6, 5, 17, 30, 40, 0, time.UTC), }, { UID: "fake_uid", SessionUID: query.SessionUID, Status: cloudmigration.SnapshotStatusCreating, + Created: time.Date(2024, 6, 5, 18, 30, 40, 0, time.UTC), }, - }, nil + } + + if query.Sort == "latest" { + sort.Slice(cloudSnapshots, func(first, second int) bool { + return cloudSnapshots[first].Created.After(cloudSnapshots[second].Created) + }) + } + if query.Limit > 0 { + return cloudSnapshots[0:min(len(cloudSnapshots), query.Limit)], nil + } + return cloudSnapshots, nil } func (m FakeServiceImpl) UploadSnapshot(ctx context.Context, sessionUid string, snapshotUid string) error { diff --git a/pkg/services/cloudmigration/cloudmigrationimpl/xorm_store.go b/pkg/services/cloudmigration/cloudmigrationimpl/xorm_store.go index 22de2474310..abccfbaf653 100644 --- a/pkg/services/cloudmigration/cloudmigrationimpl/xorm_store.go +++ b/pkg/services/cloudmigration/cloudmigrationimpl/xorm_store.go @@ -23,9 +23,10 @@ type sqlStore struct { } const ( - tableName = "cloud_migration_resource" - secretType = "cloudmigration-snapshot-encryption-key" - GetAllSnapshots = -1 + tableName = "cloud_migration_resource" + secretType = "cloudmigration-snapshot-encryption-key" + GetAllSnapshots = -1 + GetSnapshotListSortingLatest = "latest" ) func (ss *sqlStore) GetMigrationSessionByUID(ctx context.Context, uid string) (*cloudmigration.CloudMigrationSession, error) { @@ -278,7 +279,9 @@ func (ss *sqlStore) GetSnapshotList(ctx context.Context, query cloudmigration.Li offset := (query.Page - 1) * query.Limit sess.Limit(query.Limit, offset) } - sess.OrderBy("cloud_migration_snapshot.created DESC") + if query.Sort == GetSnapshotListSortingLatest { + sess.OrderBy("cloud_migration_snapshot.created DESC") + } return sess.Find(&snapshots, &cloudmigration.CloudMigrationSnapshot{ SessionUID: query.SessionUID, }) diff --git a/pkg/services/cloudmigration/cloudmigrationimpl/xorm_store_test.go b/pkg/services/cloudmigration/cloudmigrationimpl/xorm_store_test.go index 51c7529495e..58b98f6c1ad 100644 --- a/pkg/services/cloudmigration/cloudmigrationimpl/xorm_store_test.go +++ b/pkg/services/cloudmigration/cloudmigrationimpl/xorm_store_test.go @@ -3,7 +3,6 @@ package cloudmigrationimpl import ( "context" "encoding/base64" - "slices" "strconv" "testing" @@ -241,9 +240,46 @@ func TestGetSnapshotList(t *testing.T) { for _, snapshot := range snapshots { ids = append(ids, snapshot.UID) } - slices.Sort(ids) // There are 3 snapshots in the db but only 2 of them belong to this specific session. + assert.Equal(t, []string{"poiuy", "lkjhg"}, ids) + }) + + t.Run("returns only one snapshot that belongs to a session", func(t *testing.T) { + snapshots, err := s.GetSnapshotList(ctx, cloudmigration.ListSnapshotsQuery{SessionUID: sessionUID, Page: 1, Limit: 1}) + require.NoError(t, err) + assert.Len(t, snapshots, 1) + }) + + t.Run("return no snapshots if limit is set to 0", func(t *testing.T) { + snapshots, err := s.GetSnapshotList(ctx, cloudmigration.ListSnapshotsQuery{SessionUID: sessionUID, Page: 1, Limit: 0}) + require.NoError(t, err) + assert.Empty(t, snapshots) + }) + + t.Run("returns paginated snapshot that belongs to a session", func(t *testing.T) { + snapshots, err := s.GetSnapshotList(ctx, cloudmigration.ListSnapshotsQuery{SessionUID: sessionUID, Page: 2, Limit: 1}) + require.NoError(t, err) + + ids := make([]string, 0) + for _, snapshot := range snapshots { + ids = append(ids, snapshot.UID) + } + + // Return paginated snapshot of the 2 belonging to this specific session + assert.Equal(t, []string{"lkjhg"}, ids) + }) + + t.Run("returns desc sorted list of snapshots that belong to a session", func(t *testing.T) { + snapshots, err := s.GetSnapshotList(ctx, cloudmigration.ListSnapshotsQuery{SessionUID: sessionUID, Page: 1, Limit: 100, Sort: "latest"}) + require.NoError(t, err) + + ids := make([]string, 0) + for _, snapshot := range snapshots { + ids = append(ids, snapshot.UID) + } + + // Return desc sorted snapshots belonging to this specific session assert.Equal(t, []string{"lkjhg", "poiuy"}, ids) }) @@ -345,7 +381,7 @@ func setUpTest(t *testing.T) (*sqlstore.SQLStore, *sqlStore) { cloud_migration_snapshot (session_uid, uid, created, updated, finished, status) VALUES ('qwerty', 'poiuy', '2024-03-25 15:30:36.000', '2024-03-27 15:30:43.000', '2024-03-27 15:30:43.000', "finished"), - ('qwerty', 'lkjhg', '2024-03-25 15:30:36.000', '2024-03-27 15:30:43.000', '2024-03-27 15:30:43.000', "finished"), + ('qwerty', 'lkjhg', '2024-03-26 15:30:36.000', '2024-03-27 15:30:43.000', '2024-03-27 15:30:43.000', "finished"), ('zxcvbn', 'mnbvvc', '2024-03-25 15:30:36.000', '2024-03-27 15:30:43.000', '2024-03-27 15:30:43.000', "finished"); `, ) diff --git a/pkg/services/cloudmigration/model.go b/pkg/services/cloudmigration/model.go index 0eb8a8d04d6..786df738ca5 100644 --- a/pkg/services/cloudmigration/model.go +++ b/pkg/services/cloudmigration/model.go @@ -144,6 +144,7 @@ type ListSnapshotsQuery struct { SessionUID string Page int Limit int + Sort string } type UpdateSnapshotCmd struct { diff --git a/public/api-enterprise-spec.json b/public/api-enterprise-spec.json index ac04c23d278..e2d491516ff 100644 --- a/public/api-enterprise-spec.json +++ b/public/api-enterprise-spec.json @@ -5416,6 +5416,9 @@ "message": { "type": "string" }, + "name": { + "type": "string" + }, "refId": { "type": "string" }, diff --git a/public/app/features/migrate-to-cloud/api/endpoints.gen.ts b/public/app/features/migrate-to-cloud/api/endpoints.gen.ts index 447ba395491..a3cba8abefb 100644 --- a/public/app/features/migrate-to-cloud/api/endpoints.gen.ts +++ b/public/app/features/migrate-to-cloud/api/endpoints.gen.ts @@ -41,7 +41,7 @@ const injectedRtkApi = api.injectEndpoints({ getShapshotList: build.query({ query: (queryArg) => ({ url: `/cloudmigration/migration/${queryArg.uid}/snapshots`, - params: { page: queryArg.page, limit: queryArg.limit }, + params: { page: queryArg.page, limit: queryArg.limit, sort: queryArg.sort }, }), }), getCloudMigrationToken: build.query({ @@ -112,6 +112,8 @@ export type GetShapshotListApiArg = { page?: number; /** Max limit for results returned. */ limit?: number; + /** Sort with value latest to return results sorted in descending order */ + sort?: string; /** Session UID of a session */ uid: string; }; diff --git a/public/app/features/migrate-to-cloud/onprem/Page.tsx b/public/app/features/migrate-to-cloud/onprem/Page.tsx index e091c4c11da..f4808b8f246 100644 --- a/public/app/features/migrate-to-cloud/onprem/Page.tsx +++ b/public/app/features/migrate-to-cloud/onprem/Page.tsx @@ -70,7 +70,9 @@ const PAGE_SIZE = 50; function useGetLatestSnapshot(sessionUid?: string, page = 1) { const [shouldPoll, setShouldPoll] = useState(false); - const listResult = useGetShapshotListQuery(sessionUid ? { uid: sessionUid } : skipToken); + const listResult = useGetShapshotListQuery( + sessionUid ? { uid: sessionUid, page: 1, limit: 1, sort: 'latest' } : skipToken + ); const lastItem = listResult.currentData?.snapshots?.at(0); const getSnapshotQueryArgs =