diff --git a/pkg/services/cloudmigration/api/api.go b/pkg/services/cloudmigration/api/api.go index e436857ab64b..c6083e7324df 100644 --- a/pkg/services/cloudmigration/api/api.go +++ b/pkg/services/cloudmigration/api/api.go @@ -442,6 +442,17 @@ func (cma *CloudMigrationAPI) GetSnapshot(c *contextmodel.ReqContext) response.R } } + dtoStats := SnapshotResourceStats{ + Types: make(map[MigrateDataType]int, len(snapshot.StatsRollup.CountsByStatus)), + Statuses: make(map[ItemStatus]int, len(snapshot.StatsRollup.CountsByType)), + } + for s, c := range snapshot.StatsRollup.CountsByStatus { + dtoStats.Statuses[ItemStatus(s)] = c + } + for s, c := range snapshot.StatsRollup.CountsByType { + dtoStats.Types[MigrateDataType(s)] = c + } + respDto := GetSnapshotResponseDTO{ SnapshotDTO: SnapshotDTO{ SnapshotUID: snapshot.UID, @@ -450,7 +461,8 @@ func (cma *CloudMigrationAPI) GetSnapshot(c *contextmodel.ReqContext) response.R Created: snapshot.Created, Finished: snapshot.Finished, }, - Results: dtoResults, + Results: dtoResults, + StatsRollup: dtoStats, } return response.JSON(http.StatusOK, respDto) diff --git a/pkg/services/cloudmigration/api/api_test.go b/pkg/services/cloudmigration/api/api_test.go index 35fe13466f5d..73077f4ab407 100644 --- a/pkg/services/cloudmigration/api/api_test.go +++ b/pkg/services/cloudmigration/api/api_test.go @@ -471,7 +471,7 @@ func TestCloudMigrationAPI_GetSnapshot(t *testing.T) { requestUrl: "/api/cloudmigration/migration/1234/snapshot/1", basicRole: org.RoleAdmin, expectedHttpResult: http.StatusOK, - expectedBody: `{"uid":"fake_uid","status":"UNKNOWN","sessionUid":"1234","created":"0001-01-01T00:00:00Z","finished":"0001-01-01T00:00:00Z","results":[]}`, + expectedBody: `{"uid":"fake_uid","status":"UNKNOWN","sessionUid":"1234","created":"0001-01-01T00:00:00Z","finished":"0001-01-01T00:00:00Z","results":[],"stats":{"types":{},"statuses":{}}}`, }, { desc: "should return 403 if no used is not admin", diff --git a/pkg/services/cloudmigration/api/dtos.go b/pkg/services/cloudmigration/api/dtos.go index b57078958075..8ed50fb19be6 100644 --- a/pkg/services/cloudmigration/api/dtos.go +++ b/pkg/services/cloudmigration/api/dtos.go @@ -299,7 +299,13 @@ type GetSnapshotResponse struct { type GetSnapshotResponseDTO struct { SnapshotDTO - Results []MigrateDataResponseItemDTO `json:"results"` + Results []MigrateDataResponseItemDTO `json:"results"` + StatsRollup SnapshotResourceStats `json:"stats"` +} + +type SnapshotResourceStats struct { + Types map[MigrateDataType]int `json:"types"` + Statuses map[ItemStatus]int `json:"statuses"` } // swagger:parameters getShapshotList diff --git a/pkg/services/cloudmigration/cloudmigrationimpl/cloudmigration.go b/pkg/services/cloudmigration/cloudmigrationimpl/cloudmigration.go index 7b085ea15d8b..7a61dafaaf70 100644 --- a/pkg/services/cloudmigration/cloudmigrationimpl/cloudmigration.go +++ b/pkg/services/cloudmigration/cloudmigrationimpl/cloudmigration.go @@ -332,7 +332,7 @@ func (s *Service) GetSession(ctx context.Context, uid string) (*cloudmigration.C } func (s *Service) GetSessionList(ctx context.Context) (*cloudmigration.CloudMigrationSessionListResponse, error) { - values, err := s.store.GetAllCloudMigrationSessions(ctx) + values, err := s.store.GetCloudMigrationSessionList(ctx) if err != nil { return nil, err } diff --git a/pkg/services/cloudmigration/cloudmigrationimpl/store.go b/pkg/services/cloudmigration/cloudmigrationimpl/store.go index fa8552905053..3a4962b8a048 100644 --- a/pkg/services/cloudmigration/cloudmigrationimpl/store.go +++ b/pkg/services/cloudmigration/cloudmigrationimpl/store.go @@ -9,7 +9,7 @@ import ( type store interface { CreateMigrationSession(ctx context.Context, session cloudmigration.CloudMigrationSession) (*cloudmigration.CloudMigrationSession, error) GetMigrationSessionByUID(ctx context.Context, uid string) (*cloudmigration.CloudMigrationSession, error) - GetAllCloudMigrationSessions(ctx context.Context) ([]*cloudmigration.CloudMigrationSession, error) + GetCloudMigrationSessionList(ctx context.Context) ([]*cloudmigration.CloudMigrationSession, error) DeleteMigrationSessionByUID(ctx context.Context, uid string) (*cloudmigration.CloudMigrationSession, error) CreateMigrationRun(ctx context.Context, cmr cloudmigration.CloudMigrationSnapshot) (string, error) @@ -23,5 +23,6 @@ type store interface { CreateUpdateSnapshotResources(ctx context.Context, snapshotUid string, resources []cloudmigration.CloudMigrationResource) error GetSnapshotResources(ctx context.Context, snapshotUid string, page int, limit int) ([]cloudmigration.CloudMigrationResource, error) + GetSnapshotResourceStats(ctx context.Context, snapshotUid string) (*cloudmigration.SnapshotResourceStats, error) DeleteSnapshotResources(ctx context.Context, snapshotUid string) error } diff --git a/pkg/services/cloudmigration/cloudmigrationimpl/xorm_store.go b/pkg/services/cloudmigration/cloudmigrationimpl/xorm_store.go index a7c7461b5891..e7d9ba97b65c 100644 --- a/pkg/services/cloudmigration/cloudmigrationimpl/xorm_store.go +++ b/pkg/services/cloudmigration/cloudmigrationimpl/xorm_store.go @@ -20,6 +20,10 @@ type sqlStore struct { secretsService secrets.Service } +const ( + tableName = "cloud_migration_resource" +) + func (ss *sqlStore) GetMigrationSessionByUID(ctx context.Context, uid string) (*cloudmigration.CloudMigrationSession, error) { var cm cloudmigration.CloudMigrationSession err := ss.db.WithDbSession(ctx, func(sess *db.Session) error { @@ -78,9 +82,12 @@ func (ss *sqlStore) CreateMigrationSession(ctx context.Context, migration cloudm return &migration, nil } -func (ss *sqlStore) GetAllCloudMigrationSessions(ctx context.Context) ([]*cloudmigration.CloudMigrationSession, error) { +func (ss *sqlStore) GetCloudMigrationSessionList(ctx context.Context) ([]*cloudmigration.CloudMigrationSession, error) { var migrations = make([]*cloudmigration.CloudMigrationSession, 0) - err := ss.db.WithDbSession(ctx, func(sess *db.Session) error { return sess.Find(&migrations) }) + err := ss.db.WithDbSession(ctx, func(sess *db.Session) error { + sess.OrderBy("created DESC") + return sess.Find(&migrations) + }) if err != nil { return nil, err } @@ -227,6 +234,10 @@ func (ss *sqlStore) GetSnapshotByUID(ctx context.Context, uid string, resultPage if err == nil { snapshot.Resources = resources } + stats, err := ss.GetSnapshotResourceStats(ctx, uid) + if err == nil { + snapshot.StatsRollup = *stats + } return &snapshot, err } @@ -249,6 +260,12 @@ func (ss *sqlStore) GetSnapshotList(ctx context.Context, query cloudmigration.Li if err := ss.decryptKey(ctx, &snapshot); err != nil { return nil, err } + + if stats, err := ss.GetSnapshotResourceStats(ctx, snapshot.UID); err != nil { + return nil, err + } else { + snapshot.StatsRollup = *stats + } snapshots[i] = snapshot } return snapshots, nil @@ -309,6 +326,46 @@ func (ss *sqlStore) GetSnapshotResources(ctx context.Context, snapshotUid string return resources, nil } +func (ss *sqlStore) GetSnapshotResourceStats(ctx context.Context, snapshotUid string) (*cloudmigration.SnapshotResourceStats, error) { + typeCounts := make([]struct { + Count int `json:"count"` + Type string `json:"type"` + }, 0) + statusCounts := make([]struct { + Count int `json:"count"` + Status string `json:"status"` + }, 0) + err := ss.db.WithDbSession(ctx, func(sess *sqlstore.DBSession) error { + sess.Select("count(uid) as 'count', resource_type as 'type'"). + Table(tableName). + GroupBy("type"). + Where("snapshot_uid = ?", snapshotUid) + if err := sess.Find(&typeCounts); err != nil { + return err + } + sess.Select("count(uid) as 'count', status"). + Table(tableName). + GroupBy("status"). + Where("snapshot_uid = ?", snapshotUid) + return sess.Find(&statusCounts) + }) + if err != nil { + return nil, err + } + + stats := &cloudmigration.SnapshotResourceStats{ + CountsByType: make(map[cloudmigration.MigrateDataType]int, len(typeCounts)), + CountsByStatus: make(map[cloudmigration.ItemStatus]int, len(statusCounts)), + } + for _, c := range typeCounts { + stats.CountsByType[cloudmigration.MigrateDataType(c.Type)] = c.Count + } + for _, c := range statusCounts { + stats.CountsByStatus[cloudmigration.ItemStatus(c.Status)] = c.Count + } + return stats, nil +} + func (ss *sqlStore) DeleteSnapshotResources(ctx context.Context, snapshotUid string) error { return ss.db.WithDbSession(ctx, func(sess *sqlstore.DBSession) error { _, err := sess.Delete(cloudmigration.CloudMigrationResource{ diff --git a/pkg/services/cloudmigration/cloudmigrationimpl/xorm_store_test.go b/pkg/services/cloudmigration/cloudmigrationimpl/xorm_store_test.go index 83b94ba9ecfb..a963c7684f55 100644 --- a/pkg/services/cloudmigration/cloudmigrationimpl/xorm_store_test.go +++ b/pkg/services/cloudmigration/cloudmigrationimpl/xorm_store_test.go @@ -25,7 +25,7 @@ func Test_GetAllCloudMigrationSessions(t *testing.T) { ctx := context.Background() t.Run("get all cloud_migration_session entries", func(t *testing.T) { - value, err := s.GetAllCloudMigrationSessions(ctx) + value, err := s.GetCloudMigrationSessionList(ctx) require.NoError(t, err) require.Equal(t, 3, len(value)) for _, m := range value { @@ -245,6 +245,19 @@ func Test_SnapshotResources(t *testing.T) { } } + // check stats + stats, err := s.GetSnapshotResourceStats(ctx, "poiuy") + assert.NoError(t, err) + assert.Equal(t, map[cloudmigration.MigrateDataType]int{ + cloudmigration.DatasourceDataType: 2, + cloudmigration.DashboardDataType: 1, + cloudmigration.FolderDataType: 1, + }, stats.CountsByType) + assert.Equal(t, map[cloudmigration.ItemStatus]int{ + cloudmigration.ItemStatusOK: 3, + cloudmigration.ItemStatusPending: 1, + }, stats.CountsByStatus) + // delete snapshot resources err = s.DeleteSnapshotResources(ctx, "poiuy") assert.NoError(t, err) diff --git a/pkg/services/cloudmigration/model.go b/pkg/services/cloudmigration/model.go index fe257f07c502..3ab277ae1ef8 100644 --- a/pkg/services/cloudmigration/model.go +++ b/pkg/services/cloudmigration/model.go @@ -17,6 +17,8 @@ var ( ) // CloudMigration domain structs + +// CloudMigrationSession represents a configured migration token type CloudMigrationSession struct { ID int64 `xorm:"pk autoincr 'id'"` UID string `xorm:"uid"` @@ -29,6 +31,7 @@ type CloudMigrationSession struct { Updated time.Time } +// CloudMigrationSnapshot contains all of the metadata about a snapshot type CloudMigrationSnapshot struct { ID int64 `xorm:"pk autoincr 'id'"` UID string `xorm:"uid"` @@ -45,6 +48,8 @@ type CloudMigrationSnapshot struct { // Stored in the cloud_migration_resource table Resources []CloudMigrationResource `xorm:"-"` + // Derived by querying the cloud_migration_resource table + StatsRollup SnapshotResourceStats `xorm:"-"` } type SnapshotStatus string @@ -73,6 +78,28 @@ type CloudMigrationResource struct { SnapshotUID string `xorm:"snapshot_uid"` } +type MigrateDataType string + +const ( + DashboardDataType MigrateDataType = "DASHBOARD" + DatasourceDataType MigrateDataType = "DATASOURCE" + FolderDataType MigrateDataType = "FOLDER" +) + +type ItemStatus string + +const ( + ItemStatusOK ItemStatus = "OK" + ItemStatusError ItemStatus = "ERROR" + ItemStatusPending ItemStatus = "PENDING" + ItemStatusUnknown ItemStatus = "UNKNOWN" +) + +type SnapshotResourceStats struct { + CountsByType map[MigrateDataType]int + CountsByStatus map[ItemStatus]int +} + // Deprecated, use GetSnapshotResult for the async workflow func (s CloudMigrationSnapshot) GetResult() (*MigrateDataResponse, error) { result := MigrateDataResponse{ @@ -154,14 +181,6 @@ type Base64HGInstance struct { // GMS domain structs -type MigrateDataType string - -const ( - DashboardDataType MigrateDataType = "DASHBOARD" - DatasourceDataType MigrateDataType = "DATASOURCE" - FolderDataType MigrateDataType = "FOLDER" -) - type MigrateDataRequest struct { Items []MigrateDataRequestItem } @@ -173,15 +192,6 @@ type MigrateDataRequestItem struct { Data interface{} } -type ItemStatus string - -const ( - ItemStatusOK ItemStatus = "OK" - ItemStatusError ItemStatus = "ERROR" - ItemStatusPending ItemStatus = "PENDING" - ItemStatusUnknown ItemStatus = "UNKNOWN" -) - type MigrateDataResponse struct { RunUID string Items []CloudMigrationResource diff --git a/public/api-enterprise-spec.json b/public/api-enterprise-spec.json index a33e984f9e15..c0a852028ddb 100644 --- a/public/api-enterprise-spec.json +++ b/public/api-enterprise-spec.json @@ -4793,6 +4793,9 @@ "sessionUid": { "type": "string" }, + "stats": { + "$ref": "#/definitions/SnapshotResourceStats" + }, "status": { "type": "string", "enum": [ @@ -7235,6 +7238,25 @@ } } }, + "SnapshotResourceStats": { + "type": "object", + "properties": { + "statuses": { + "type": "object", + "additionalProperties": { + "type": "integer", + "format": "int64" + } + }, + "types": { + "type": "object", + "additionalProperties": { + "type": "integer", + "format": "int64" + } + } + } + }, "State": { "description": "+enum", "type": "string" diff --git a/public/api-merged.json b/public/api-merged.json index f30ece0f754e..05a18e5c7c25 100644 --- a/public/api-merged.json +++ b/public/api-merged.json @@ -15588,6 +15588,9 @@ "sessionUid": { "type": "string" }, + "stats": { + "$ref": "#/definitions/SnapshotResourceStats" + }, "status": { "type": "string", "enum": [ @@ -20387,6 +20390,25 @@ } } }, + "SnapshotResourceStats": { + "type": "object", + "properties": { + "statuses": { + "type": "object", + "additionalProperties": { + "type": "integer", + "format": "int64" + } + }, + "types": { + "type": "object", + "additionalProperties": { + "type": "integer", + "format": "int64" + } + } + } + }, "Span": { "type": "object", "title": "A Span defines a continuous sequence of buckets.", diff --git a/public/openapi3.json b/public/openapi3.json index 01b0937f681a..9af294717752 100644 --- a/public/openapi3.json +++ b/public/openapi3.json @@ -5685,6 +5685,9 @@ "sessionUid": { "type": "string" }, + "stats": { + "$ref": "#/components/schemas/SnapshotResourceStats" + }, "status": { "enum": [ "INITIALIZING", @@ -10484,6 +10487,25 @@ }, "type": "object" }, + "SnapshotResourceStats": { + "properties": { + "statuses": { + "additionalProperties": { + "format": "int64", + "type": "integer" + }, + "type": "object" + }, + "types": { + "additionalProperties": { + "format": "int64", + "type": "integer" + }, + "type": "object" + } + }, + "type": "object" + }, "Span": { "properties": { "Length": {