Dashboards: Backend schema version migration (#99392)
This commit is contained in:
@@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user