Dashboards: Backend schema version migration (#99392)

This commit is contained in:
Todd Treece
2025-01-23 11:40:22 -05:00
committed by GitHub
parent 192a81d07f
commit a4ef1f76e4
10 changed files with 625 additions and 7 deletions
@@ -0,0 +1,40 @@
package schemaversion
import "fmt"
var _ error = &MinimumVersionError{}
var _ error = &MigrationError{}
// MinimumVersionError is an error that is returned when the schema version is below the minimum version.
func NewMinimumVersionError(inputVersion int) *MinimumVersionError {
return &MinimumVersionError{inputVersion: inputVersion}
}
// MinimumVersionError is an error type for minimum version errors.
type MinimumVersionError struct {
inputVersion int
}
func (e *MinimumVersionError) Error() string {
return fmt.Errorf("input schema version is below minimum version. input: %d minimum: %d", e.inputVersion, MINIUM_VERSION).Error()
}
// ErrMigrationFailed is an error that is returned when a migration fails.
func NewMigrationError(msg string, currentVersion, targetVersion int) *MigrationError {
return &MigrationError{
msg: msg,
targetVersion: targetVersion,
currentVersion: currentVersion,
}
}
// MigrationError is an error type for migration errors.
type MigrationError struct {
msg string
targetVersion int
currentVersion int
}
func (e *MigrationError) Error() string {
return fmt.Errorf("schema migration from version %d to %d failed: %v", e.currentVersion, e.targetVersion, e.msg).Error()
}
@@ -0,0 +1,31 @@
package schemaversion
import "strconv"
type SchemaVersionMigrationFunc func(map[string]interface{}) error
const (
MINIUM_VERSION = 39
LATEST_VERSION = 40
)
var Migrations = map[int]SchemaVersionMigrationFunc{
40: V40,
}
func GetSchemaVersion(dash map[string]interface{}) int {
if v, ok := dash["schemaVersion"]; ok {
switch v := v.(type) {
case int:
return v
case float64:
return int(v)
case string:
if version, err := strconv.Atoi(v); err == nil {
return version
}
return 0
}
}
return 0
}
@@ -0,0 +1,57 @@
package schemaversion_test
import (
"testing"
"github.com/grafana/grafana/pkg/apis/dashboard/migration/schemaversion"
"github.com/stretchr/testify/require"
)
func TestGetSchemaVersion(t *testing.T) {
tests := []struct {
name string
dash map[string]interface{}
expected int
}{
{
name: "schemaVersion as int",
dash: map[string]interface{}{
"schemaVersion": 16,
},
expected: 16,
},
{
name: "schemaVersion as float64",
dash: map[string]interface{}{
"schemaVersion": 40.2345,
},
expected: 40,
},
{
name: "schemaVersion is not set",
dash: map[string]interface{}{},
expected: 0,
},
{
name: "schemaVersion as string int",
dash: map[string]interface{}{
"schemaVersion": "5",
},
expected: 5,
},
{
name: "schemaVersion as invalid string",
dash: map[string]interface{}{
"schemaVersion": "foo",
},
expected: 0,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := schemaversion.GetSchemaVersion(tt.dash)
require.Equal(t, tt.expected, result)
})
}
}
@@ -0,0 +1,9 @@
package schemaversion
func V40(dash map[string]interface{}) error {
dash["schemaVersion"] = int(40)
if _, ok := dash["refresh"].(string); !ok {
dash["refresh"] = ""
}
return nil
}
@@ -0,0 +1,70 @@
package schemaversion_test
import (
"testing"
"github.com/grafana/grafana/pkg/apis/dashboard/migration/schemaversion"
"github.com/stretchr/testify/require"
)
func TestV40(t *testing.T) {
tests := []migrationTestCase{
{
name: "refresh not set",
input: map[string]interface{}{
"title": "Test Dashboard",
},
expected: map[string]interface{}{
"title": "Test Dashboard",
"schemaVersion": 40,
"refresh": "",
},
},
{
name: "boolean refresh value is converted to an empty string",
input: map[string]interface{}{
"title": "Test Dashboard",
"schemaVersion": 39,
"refresh": true,
},
expected: map[string]interface{}{
"title": "Test Dashboard",
"schemaVersion": 40,
"refresh": "",
},
},
{
name: "string refresh value is not converted",
input: map[string]interface{}{
"title": "Test Dashboard",
"schemaVersion": 39,
"refresh": "1m",
},
expected: map[string]interface{}{
"title": "Test Dashboard",
"schemaVersion": 40,
"refresh": "1m",
},
},
}
runMigrationTests(t, tests, schemaversion.V40)
}
type migrationTestCase struct {
name string
input map[string]interface{}
expected map[string]interface{}
}
func runMigrationTests(t *testing.T, testCases []migrationTestCase, migrationFunc schemaversion.SchemaVersionMigrationFunc) {
t.Helper()
for _, tt := range testCases {
t.Run(tt.name, func(t *testing.T) {
err := migrationFunc(tt.input)
require.NoError(t, err)
require.Equal(t, tt.expected, tt.input)
})
}
}