Dashboard Migrations: V23 handle variable multi and current properties (#108937)
Co-authored-by: Ivan Ortega <ivanortegaalba@gmail.com>
This commit is contained in:
co-authored by
Ivan Ortega
parent
a5abc6727f
commit
4b78e54304
@@ -5,7 +5,7 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
MIN_VERSION = 23
|
||||
MIN_VERSION = 22
|
||||
LATEST_VERSION = 41
|
||||
)
|
||||
|
||||
@@ -38,6 +38,7 @@ type PanelPluginInfoProvider interface {
|
||||
|
||||
func GetMigrations(dsInfoProvider DataSourceInfoProvider, panelProvider PanelPluginInfoProvider) map[int]SchemaVersionMigrationFunc {
|
||||
return map[int]SchemaVersionMigrationFunc{
|
||||
23: V23,
|
||||
24: V24(panelProvider),
|
||||
25: V25,
|
||||
26: V26,
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
package schemaversion
|
||||
|
||||
import "github.com/grafana/grafana/apps/dashboard/pkg/migration/utils"
|
||||
|
||||
// V23 migrates multi variables to ensure their current property is aligned with their multi property.
|
||||
// This migration ensures that variables with multi=true have current.value and current.text as arrays,
|
||||
// and variables with multi=false have current.value and current.text as single values.
|
||||
//
|
||||
// Example before migration:
|
||||
//
|
||||
// "templating": {
|
||||
// "list": [
|
||||
// { "type": "query", "multi": true, "current": { "value": "A", "text": "A" } },
|
||||
// { "type": "query", "multi": false, "current": { "value": ["B"], "text": ["B"] } }
|
||||
// ]
|
||||
// }
|
||||
//
|
||||
// Example after migration:
|
||||
//
|
||||
// "templating": {
|
||||
// "list": [
|
||||
// { "type": "query", "multi": true, "current": { "value": ["A"], "text": ["A"] } },
|
||||
// { "type": "query", "multi": false, "current": { "value": "B", "text": "B" } }
|
||||
// ]
|
||||
// }
|
||||
func V23(dashboard map[string]interface{}) error {
|
||||
dashboard["schemaVersion"] = 23
|
||||
|
||||
templating, ok := dashboard["templating"].(map[string]interface{})
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
list, ok := templating["list"].([]interface{})
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, v := range list {
|
||||
variable, ok := v.(map[string]interface{})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
if !isMulti(variable) {
|
||||
continue
|
||||
}
|
||||
|
||||
current, ok := variable["current"].(map[string]interface{})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
if isEmptyObject(current) {
|
||||
continue
|
||||
}
|
||||
|
||||
multi, ok := variable["multi"].(bool)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
variable["current"] = alignCurrentWithMulti(current, multi)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// isMulti checks if a variable supports multi-selection
|
||||
func isMulti(variable map[string]interface{}) bool {
|
||||
_, hasMulti := variable["multi"]
|
||||
return hasMulti
|
||||
}
|
||||
|
||||
func isEmptyObject(value interface{}) bool {
|
||||
if value == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
obj, ok := value.(map[string]interface{})
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
return len(obj) == 0
|
||||
}
|
||||
|
||||
// alignCurrentWithMulti aligns the current property with the multi property
|
||||
func alignCurrentWithMulti(current map[string]interface{}, multi bool) map[string]interface{} {
|
||||
if current == nil {
|
||||
return current
|
||||
}
|
||||
|
||||
result := make(map[string]interface{})
|
||||
for k, v := range current {
|
||||
result[k] = v
|
||||
}
|
||||
|
||||
if multi {
|
||||
// Convert single values to arrays
|
||||
if value, ok := result["value"]; ok {
|
||||
if !utils.IsArray(value) {
|
||||
result["value"] = []interface{}{value}
|
||||
}
|
||||
}
|
||||
if text, ok := result["text"]; ok {
|
||||
if !utils.IsArray(text) {
|
||||
result["text"] = []interface{}{text}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Convert arrays to single values
|
||||
if value, ok := result["value"]; ok {
|
||||
if utils.IsArray(value) {
|
||||
if arr, ok := value.([]interface{}); ok && len(arr) > 0 {
|
||||
result["value"] = arr[0]
|
||||
} else {
|
||||
result["value"] = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
if text, ok := result["text"]; ok {
|
||||
if utils.IsArray(text) {
|
||||
if arr, ok := text.([]interface{}); ok && len(arr) > 0 {
|
||||
result["text"] = arr[0]
|
||||
} else {
|
||||
result["text"] = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
@@ -0,0 +1,229 @@
|
||||
package schemaversion_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/grafana/apps/dashboard/pkg/migration/schemaversion"
|
||||
)
|
||||
|
||||
func TestV23(t *testing.T) {
|
||||
tests := []migrationTestCase{
|
||||
{
|
||||
name: "multi variable with single value gets converted to array",
|
||||
input: map[string]interface{}{
|
||||
"title": "V23 Multi Variable Single Value Test",
|
||||
"schemaVersion": 22,
|
||||
"templating": map[string]interface{}{
|
||||
"list": []interface{}{
|
||||
map[string]interface{}{
|
||||
"type": "query",
|
||||
"name": "multi_single_value",
|
||||
"multi": true,
|
||||
"current": map[string]interface{}{"value": "A", "text": "A", "selected": true},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: map[string]interface{}{
|
||||
"title": "V23 Multi Variable Single Value Test",
|
||||
"schemaVersion": 23,
|
||||
"templating": map[string]interface{}{
|
||||
"list": []interface{}{
|
||||
map[string]interface{}{
|
||||
"type": "query",
|
||||
"name": "multi_single_value",
|
||||
"multi": true,
|
||||
"current": map[string]interface{}{"value": []interface{}{"A"}, "text": []interface{}{"A"}, "selected": true},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "multi variable with array value stays as array",
|
||||
input: map[string]interface{}{
|
||||
"title": "V23 Multi Variable Array Value Test",
|
||||
"schemaVersion": 22,
|
||||
"templating": map[string]interface{}{
|
||||
"list": []interface{}{
|
||||
map[string]interface{}{
|
||||
"type": "query",
|
||||
"name": "multi_array_value",
|
||||
"multi": true,
|
||||
"current": map[string]interface{}{"value": []interface{}{"B", "C"}, "text": []interface{}{"B", "C"}, "selected": true},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: map[string]interface{}{
|
||||
"title": "V23 Multi Variable Array Value Test",
|
||||
"schemaVersion": 23,
|
||||
"templating": map[string]interface{}{
|
||||
"list": []interface{}{
|
||||
map[string]interface{}{
|
||||
"type": "query",
|
||||
"name": "multi_array_value",
|
||||
"multi": true,
|
||||
"current": map[string]interface{}{"value": []interface{}{"B", "C"}, "text": []interface{}{"B", "C"}, "selected": true},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "non-multi variable with array value gets converted to single value",
|
||||
input: map[string]interface{}{
|
||||
"title": "V23 Non-Multi Variable Array Value Test",
|
||||
"schemaVersion": 22,
|
||||
"templating": map[string]interface{}{
|
||||
"list": []interface{}{
|
||||
map[string]interface{}{
|
||||
"type": "query",
|
||||
"name": "non_multi_array_value",
|
||||
"multi": false,
|
||||
"current": map[string]interface{}{"value": []interface{}{"D"}, "text": []interface{}{"D"}, "selected": true},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: map[string]interface{}{
|
||||
"title": "V23 Non-Multi Variable Array Value Test",
|
||||
"schemaVersion": 23,
|
||||
"templating": map[string]interface{}{
|
||||
"list": []interface{}{
|
||||
map[string]interface{}{
|
||||
"type": "query",
|
||||
"name": "non_multi_array_value",
|
||||
"multi": false,
|
||||
"current": map[string]interface{}{"value": "D", "text": "D", "selected": true},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "non-multi variable with single value stays as single value",
|
||||
input: map[string]interface{}{
|
||||
"title": "V23 Non-Multi Variable Single Value Test",
|
||||
"schemaVersion": 22,
|
||||
"templating": map[string]interface{}{
|
||||
"list": []interface{}{
|
||||
map[string]interface{}{
|
||||
"type": "query",
|
||||
"name": "non_multi_single_value",
|
||||
"multi": false,
|
||||
"current": map[string]interface{}{"value": "E", "text": "E", "selected": true},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: map[string]interface{}{
|
||||
"title": "V23 Non-Multi Variable Single Value Test",
|
||||
"schemaVersion": 23,
|
||||
"templating": map[string]interface{}{
|
||||
"list": []interface{}{
|
||||
map[string]interface{}{
|
||||
"type": "query",
|
||||
"name": "non_multi_single_value",
|
||||
"multi": false,
|
||||
"current": map[string]interface{}{"value": "E", "text": "E", "selected": true},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "variable without multi property is unchanged",
|
||||
input: map[string]interface{}{
|
||||
"title": "V23 No Multi Property Test",
|
||||
"schemaVersion": 22,
|
||||
"templating": map[string]interface{}{
|
||||
"list": []interface{}{
|
||||
map[string]interface{}{
|
||||
"type": "query",
|
||||
"name": "no_multi_property",
|
||||
"current": map[string]interface{}{"value": "F", "text": "F", "selected": true},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: map[string]interface{}{
|
||||
"title": "V23 No Multi Property Test",
|
||||
"schemaVersion": 23,
|
||||
"templating": map[string]interface{}{
|
||||
"list": []interface{}{
|
||||
map[string]interface{}{
|
||||
"type": "query",
|
||||
"name": "no_multi_property",
|
||||
"current": map[string]interface{}{"value": "F", "text": "F", "selected": true},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "variable with empty current is unchanged",
|
||||
input: map[string]interface{}{
|
||||
"title": "V23 Empty Current Test",
|
||||
"schemaVersion": 22,
|
||||
"templating": map[string]interface{}{
|
||||
"list": []interface{}{
|
||||
map[string]interface{}{
|
||||
"type": "query",
|
||||
"name": "empty_current",
|
||||
"multi": true,
|
||||
"current": map[string]interface{}{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: map[string]interface{}{
|
||||
"title": "V23 Empty Current Test",
|
||||
"schemaVersion": 23,
|
||||
"templating": map[string]interface{}{
|
||||
"list": []interface{}{
|
||||
map[string]interface{}{
|
||||
"type": "query",
|
||||
"name": "empty_current",
|
||||
"multi": true,
|
||||
"current": map[string]interface{}{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "variable with nil current is unchanged",
|
||||
input: map[string]interface{}{
|
||||
"title": "V23 Nil Current Test",
|
||||
"schemaVersion": 22,
|
||||
"templating": map[string]interface{}{
|
||||
"list": []interface{}{
|
||||
map[string]interface{}{
|
||||
"type": "query",
|
||||
"name": "nil_current",
|
||||
"multi": true,
|
||||
"current": nil,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: map[string]interface{}{
|
||||
"title": "V23 Nil Current Test",
|
||||
"schemaVersion": 23,
|
||||
"templating": map[string]interface{}{
|
||||
"list": []interface{}{
|
||||
map[string]interface{}{
|
||||
"type": "query",
|
||||
"name": "nil_current",
|
||||
"multi": true,
|
||||
"current": nil,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
runMigrationTests(t, tests, schemaversion.V23)
|
||||
}
|
||||
Reference in New Issue
Block a user