Dashboard Migrations: V29 query variable refresh property and options (#108088)
* Dashboard Migrations: V31 LabelsToFields-Merge Migration * Dashboard Migrations: V32 No-op migration * simplify * Refactor to reduce nesting * Dashboard Migrations: V30 value mappings and tooltip options * Do not automigrate since graph is migrated in v27 * Refactor to reduce nesting * Add test case for invalid mapping * migrate to v29 --------- Co-authored-by: Ivan Ortega <ivanortegaalba@gmail.com>
This commit is contained in:
co-authored by
Ivan Ortega
parent
1200b684c6
commit
2f8ec01c6c
@@ -5,7 +5,7 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
MIN_VERSION = 29
|
||||
MIN_VERSION = 28
|
||||
LATEST_VERSION = 41
|
||||
)
|
||||
|
||||
@@ -26,6 +26,7 @@ type DataSourceInfoProvider interface {
|
||||
|
||||
func GetMigrations(dsInfoProvider DataSourceInfoProvider) map[int]SchemaVersionMigrationFunc {
|
||||
return map[int]SchemaVersionMigrationFunc{
|
||||
29: V29,
|
||||
30: V30,
|
||||
31: V31,
|
||||
32: V32,
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
package schemaversion
|
||||
|
||||
// V29 migrates query variables to ensure their refresh property is set to 1 (on dashboard load)
|
||||
// if it is not 1 or 2, and clears their options array if present.
|
||||
//
|
||||
// Example before migration:
|
||||
//
|
||||
// "templating": {
|
||||
// "list": [
|
||||
// { "type": "query", "refresh": 0, "options": [{ "text": "A", "value": "A" }] },
|
||||
// { "type": "query", "refresh": 2, "options": [{ "text": "B", "value": "B" }] },
|
||||
// { "type": "query", "options": [{ "text": "C", "value": "C" }] }
|
||||
// ]
|
||||
// }
|
||||
//
|
||||
// Example after migration:
|
||||
//
|
||||
// "templating": {
|
||||
// "list": [
|
||||
// { "type": "query", "refresh": 1, "options": [] },
|
||||
// { "type": "query", "refresh": 2, "options": [] },
|
||||
// { "type": "query", "refresh": 1, "options": [] }
|
||||
// ]
|
||||
// }
|
||||
func V29(dashboard map[string]interface{}) error {
|
||||
dashboard["schemaVersion"] = 29
|
||||
|
||||
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 variable["type"] != "query" {
|
||||
continue
|
||||
}
|
||||
// Set refresh to 1 if not 1 or 2
|
||||
refresh, hasRefresh := variable["refresh"]
|
||||
refreshInt := 0
|
||||
if r, ok := refresh.(int); ok {
|
||||
refreshInt = r
|
||||
}
|
||||
if !hasRefresh || (refreshInt != 1 && refreshInt != 2) {
|
||||
variable["refresh"] = 1
|
||||
}
|
||||
// Clear options if present
|
||||
if _, hasOptions := variable["options"]; hasOptions {
|
||||
variable["options"] = []interface{}{}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
package schemaversion_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/grafana/apps/dashboard/pkg/migration/schemaversion"
|
||||
)
|
||||
|
||||
func TestV29(t *testing.T) {
|
||||
tests := []migrationTestCase{
|
||||
{
|
||||
name: "query variables get migrated with refresh and options",
|
||||
input: map[string]interface{}{
|
||||
"title": "V29 Query Variables Migration Test Dashboard",
|
||||
"schemaVersion": 28,
|
||||
"templating": map[string]interface{}{
|
||||
"list": []interface{}{
|
||||
map[string]interface{}{"type": "query", "name": "never_refresh_with_options", "options": []interface{}{map[string]interface{}{"text": "A", "value": "A"}}, "refresh": 0},
|
||||
map[string]interface{}{"type": "query", "name": "never_refresh_without_options", "options": []interface{}{}, "refresh": 0},
|
||||
map[string]interface{}{"type": "query", "name": "dashboard_refresh_with_options", "options": []interface{}{map[string]interface{}{"text": "A", "value": "A"}}, "refresh": 1},
|
||||
map[string]interface{}{"type": "query", "name": "dashboard_refresh_without_options", "options": []interface{}{}, "refresh": 1},
|
||||
map[string]interface{}{"type": "query", "name": "timerange_refresh_with_options", "options": []interface{}{map[string]interface{}{"text": "A", "value": "A"}}, "refresh": 2},
|
||||
map[string]interface{}{"type": "query", "name": "timerange_refresh_without_options", "options": []interface{}{}, "refresh": 2},
|
||||
map[string]interface{}{"type": "query", "name": "no_refresh_with_options", "options": []interface{}{map[string]interface{}{"text": "A", "value": "A"}}},
|
||||
map[string]interface{}{"type": "query", "name": "no_refresh_without_options", "options": []interface{}{}},
|
||||
map[string]interface{}{"type": "query", "name": "unknown_refresh_with_options", "options": []interface{}{map[string]interface{}{"text": "A", "value": "A"}}, "refresh": 2001},
|
||||
map[string]interface{}{"type": "query", "name": "unknown_refresh_without_options", "options": []interface{}{}, "refresh": 2001},
|
||||
map[string]interface{}{"type": "custom", "name": "custom", "options": []interface{}{map[string]interface{}{"text": "custom", "value": "custom"}}},
|
||||
map[string]interface{}{"type": "textbox", "name": "textbox", "options": []interface{}{map[string]interface{}{"text": "Hello", "value": "World"}}},
|
||||
map[string]interface{}{"type": "datasource", "name": "datasource", "options": []interface{}{map[string]interface{}{"text": "ds", "value": "ds"}}},
|
||||
map[string]interface{}{"type": "interval", "name": "interval", "options": []interface{}{map[string]interface{}{"text": "1m", "value": "1m"}}},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: map[string]interface{}{
|
||||
"title": "V29 Query Variables Migration Test Dashboard",
|
||||
"schemaVersion": 29,
|
||||
"templating": map[string]interface{}{
|
||||
"list": []interface{}{
|
||||
map[string]interface{}{"type": "query", "name": "never_refresh_with_options", "options": []interface{}{}, "refresh": 1},
|
||||
map[string]interface{}{"type": "query", "name": "never_refresh_without_options", "options": []interface{}{}, "refresh": 1},
|
||||
map[string]interface{}{"type": "query", "name": "dashboard_refresh_with_options", "options": []interface{}{}, "refresh": 1},
|
||||
map[string]interface{}{"type": "query", "name": "dashboard_refresh_without_options", "options": []interface{}{}, "refresh": 1},
|
||||
map[string]interface{}{"type": "query", "name": "timerange_refresh_with_options", "options": []interface{}{}, "refresh": 2},
|
||||
map[string]interface{}{"type": "query", "name": "timerange_refresh_without_options", "options": []interface{}{}, "refresh": 2},
|
||||
map[string]interface{}{"type": "query", "name": "no_refresh_with_options", "options": []interface{}{}, "refresh": 1},
|
||||
map[string]interface{}{"type": "query", "name": "no_refresh_without_options", "options": []interface{}{}, "refresh": 1},
|
||||
map[string]interface{}{"type": "query", "name": "unknown_refresh_with_options", "options": []interface{}{}, "refresh": 1},
|
||||
map[string]interface{}{"type": "query", "name": "unknown_refresh_without_options", "options": []interface{}{}, "refresh": 1},
|
||||
map[string]interface{}{"type": "custom", "name": "custom", "options": []interface{}{map[string]interface{}{"text": "custom", "value": "custom"}}},
|
||||
map[string]interface{}{"type": "textbox", "name": "textbox", "options": []interface{}{map[string]interface{}{"text": "Hello", "value": "World"}}},
|
||||
map[string]interface{}{"type": "datasource", "name": "datasource", "options": []interface{}{map[string]interface{}{"text": "ds", "value": "ds"}}},
|
||||
map[string]interface{}{"type": "interval", "name": "interval", "options": []interface{}{map[string]interface{}{"text": "1m", "value": "1m"}}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "non-query variables remain unchanged",
|
||||
input: map[string]interface{}{
|
||||
"title": "V29 Non-Query Variables Test Dashboard",
|
||||
"schemaVersion": 28,
|
||||
"templating": map[string]interface{}{
|
||||
"list": []interface{}{
|
||||
map[string]interface{}{"type": "custom", "name": "custom", "options": []interface{}{map[string]interface{}{"text": "custom", "value": "custom"}}},
|
||||
map[string]interface{}{"type": "textbox", "name": "textbox", "options": []interface{}{map[string]interface{}{"text": "Hello", "value": "World"}}},
|
||||
map[string]interface{}{"type": "datasource", "name": "datasource", "options": []interface{}{map[string]interface{}{"text": "ds", "value": "ds"}}},
|
||||
map[string]interface{}{"type": "interval", "name": "interval", "options": []interface{}{map[string]interface{}{"text": "1m", "value": "1m"}}},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: map[string]interface{}{
|
||||
"title": "V29 Non-Query Variables Test Dashboard",
|
||||
"schemaVersion": 29,
|
||||
"templating": map[string]interface{}{
|
||||
"list": []interface{}{
|
||||
map[string]interface{}{"type": "custom", "name": "custom", "options": []interface{}{map[string]interface{}{"text": "custom", "value": "custom"}}},
|
||||
map[string]interface{}{"type": "textbox", "name": "textbox", "options": []interface{}{map[string]interface{}{"text": "Hello", "value": "World"}}},
|
||||
map[string]interface{}{"type": "datasource", "name": "datasource", "options": []interface{}{map[string]interface{}{"text": "ds", "value": "ds"}}},
|
||||
map[string]interface{}{"type": "interval", "name": "interval", "options": []interface{}{map[string]interface{}{"text": "1m", "value": "1m"}}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "all query variables should have options removed",
|
||||
input: map[string]interface{}{
|
||||
"title": "V29 Query Variables Options Removal Test Dashboard",
|
||||
"schemaVersion": 28,
|
||||
"templating": map[string]interface{}{
|
||||
"list": []interface{}{
|
||||
map[string]interface{}{"type": "query", "name": "query1", "options": []interface{}{map[string]interface{}{"text": "A", "value": "A"}}},
|
||||
map[string]interface{}{"type": "query", "name": "query2", "options": []interface{}{}, "refresh": 1},
|
||||
map[string]interface{}{"type": "query", "name": "query3", "options": []interface{}{map[string]interface{}{"text": "B", "value": "B"}, map[string]interface{}{"text": "C", "value": "C"}}, "refresh": 2},
|
||||
map[string]interface{}{"type": "query", "name": "query4", "options": []interface{}{map[string]interface{}{"text": "D", "value": "D"}}, "refresh": 0},
|
||||
map[string]interface{}{"type": "query", "name": "query5", "options": []interface{}{map[string]interface{}{"text": "E", "value": "E"}}, "refresh": 2001},
|
||||
map[string]interface{}{"type": "query", "name": "query6", "options": []interface{}{}},
|
||||
map[string]interface{}{"type": "query", "name": "query7", "options": []interface{}{map[string]interface{}{"text": "F", "value": "F"}}, "refresh": 1},
|
||||
map[string]interface{}{"type": "query", "name": "query8", "options": []interface{}{map[string]interface{}{"text": "G", "value": "G"}}, "refresh": 2},
|
||||
map[string]interface{}{"type": "query", "name": "query9", "options": []interface{}{map[string]interface{}{"text": "H", "value": "H"}}},
|
||||
map[string]interface{}{"type": "query", "name": "query10", "options": []interface{}{map[string]interface{}{"text": "I", "value": "I"}}, "refresh": 999},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: map[string]interface{}{
|
||||
"title": "V29 Query Variables Options Removal Test Dashboard",
|
||||
"schemaVersion": 29,
|
||||
"templating": map[string]interface{}{
|
||||
"list": []interface{}{
|
||||
map[string]interface{}{"type": "query", "name": "query1", "options": []interface{}{}, "refresh": 1},
|
||||
map[string]interface{}{"type": "query", "name": "query2", "options": []interface{}{}, "refresh": 1},
|
||||
map[string]interface{}{"type": "query", "name": "query3", "options": []interface{}{}, "refresh": 2},
|
||||
map[string]interface{}{"type": "query", "name": "query4", "options": []interface{}{}, "refresh": 1},
|
||||
map[string]interface{}{"type": "query", "name": "query5", "options": []interface{}{}, "refresh": 1},
|
||||
map[string]interface{}{"type": "query", "name": "query6", "options": []interface{}{}, "refresh": 1},
|
||||
map[string]interface{}{"type": "query", "name": "query7", "options": []interface{}{}, "refresh": 1},
|
||||
map[string]interface{}{"type": "query", "name": "query8", "options": []interface{}{}, "refresh": 2},
|
||||
map[string]interface{}{"type": "query", "name": "query9", "options": []interface{}{}, "refresh": 1},
|
||||
map[string]interface{}{"type": "query", "name": "query10", "options": []interface{}{}, "refresh": 1},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
runMigrationTests(t, tests, schemaversion.V29)
|
||||
}
|
||||
+137
@@ -0,0 +1,137 @@
|
||||
{
|
||||
"title": "V29 Query Variables Refresh and Options Migration Test Dashboard",
|
||||
"schemaVersion": 28,
|
||||
"templating": {
|
||||
"list": [
|
||||
{
|
||||
"name": "never_refresh_with_options",
|
||||
"type": "query",
|
||||
"datasource": "prometheus",
|
||||
"options": [
|
||||
{"text": "A", "value": "A"},
|
||||
{"text": "B", "value": "B"}
|
||||
],
|
||||
"refresh": 0
|
||||
},
|
||||
{
|
||||
"name": "never_refresh_without_options",
|
||||
"type": "query",
|
||||
"datasource": "prometheus",
|
||||
"options": [],
|
||||
"refresh": 0
|
||||
},
|
||||
{
|
||||
"name": "dashboard_refresh_with_options",
|
||||
"type": "query",
|
||||
"datasource": "prometheus",
|
||||
"options": [
|
||||
{"text": "C", "value": "C"}
|
||||
],
|
||||
"refresh": 1
|
||||
},
|
||||
{
|
||||
"name": "dashboard_refresh_without_options",
|
||||
"type": "query",
|
||||
"datasource": "prometheus",
|
||||
"options": [],
|
||||
"refresh": 1
|
||||
},
|
||||
{
|
||||
"name": "timerange_refresh_with_options",
|
||||
"type": "query",
|
||||
"datasource": "prometheus",
|
||||
"options": [
|
||||
{"text": "D", "value": "D"},
|
||||
{"text": "E", "value": "E"}
|
||||
],
|
||||
"refresh": 2
|
||||
},
|
||||
{
|
||||
"name": "timerange_refresh_without_options",
|
||||
"type": "query",
|
||||
"datasource": "prometheus",
|
||||
"options": [],
|
||||
"refresh": 2
|
||||
},
|
||||
{
|
||||
"name": "no_refresh_with_options",
|
||||
"type": "query",
|
||||
"datasource": "prometheus",
|
||||
"options": [
|
||||
{"text": "F", "value": "F"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "no_refresh_without_options",
|
||||
"type": "query",
|
||||
"datasource": "prometheus",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "unknown_refresh_with_options",
|
||||
"type": "query",
|
||||
"datasource": "prometheus",
|
||||
"options": [
|
||||
{"text": "G", "value": "G"}
|
||||
],
|
||||
"refresh": 2001
|
||||
},
|
||||
{
|
||||
"name": "unknown_refresh_without_options",
|
||||
"type": "query",
|
||||
"datasource": "prometheus",
|
||||
"options": [],
|
||||
"refresh": 2001
|
||||
},
|
||||
{
|
||||
"name": "custom_variable",
|
||||
"type": "custom",
|
||||
"options": [
|
||||
{"text": "custom", "value": "custom"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "textbox_variable",
|
||||
"type": "textbox",
|
||||
"options": [
|
||||
{"text": "Hello", "value": "World"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "datasource_variable",
|
||||
"type": "datasource",
|
||||
"options": [
|
||||
{"text": "ds", "value": "ds"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "interval_variable",
|
||||
"type": "interval",
|
||||
"options": [
|
||||
{"text": "1m", "value": "1m"}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"panels": [
|
||||
{
|
||||
"id": 1,
|
||||
"title": "Test Panel",
|
||||
"type": "timeseries",
|
||||
"datasource": "prometheus",
|
||||
"targets": [
|
||||
{
|
||||
"refId": "A",
|
||||
"expr": "up"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"time": {
|
||||
"from": "now-6h",
|
||||
"to": "now"
|
||||
},
|
||||
"timepicker": {
|
||||
"refresh_intervals": ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"]
|
||||
}
|
||||
}
|
||||
+176
@@ -0,0 +1,176 @@
|
||||
{
|
||||
"panels": [
|
||||
{
|
||||
"datasource": {
|
||||
"uid": "prometheus"
|
||||
},
|
||||
"id": 1,
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
"uid": "prometheus"
|
||||
},
|
||||
"expr": "up",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Test Panel",
|
||||
"type": "timeseries"
|
||||
}
|
||||
],
|
||||
"refresh": "",
|
||||
"schemaVersion": 41,
|
||||
"templating": {
|
||||
"list": [
|
||||
{
|
||||
"datasource": {
|
||||
"uid": "prometheus"
|
||||
},
|
||||
"name": "never_refresh_with_options",
|
||||
"options": [],
|
||||
"refresh": 1,
|
||||
"type": "query"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"uid": "prometheus"
|
||||
},
|
||||
"name": "never_refresh_without_options",
|
||||
"options": [],
|
||||
"refresh": 1,
|
||||
"type": "query"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"uid": "prometheus"
|
||||
},
|
||||
"name": "dashboard_refresh_with_options",
|
||||
"options": [],
|
||||
"refresh": 1,
|
||||
"type": "query"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"uid": "prometheus"
|
||||
},
|
||||
"name": "dashboard_refresh_without_options",
|
||||
"options": [],
|
||||
"refresh": 1,
|
||||
"type": "query"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"uid": "prometheus"
|
||||
},
|
||||
"name": "timerange_refresh_with_options",
|
||||
"options": [],
|
||||
"refresh": 1,
|
||||
"type": "query"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"uid": "prometheus"
|
||||
},
|
||||
"name": "timerange_refresh_without_options",
|
||||
"options": [],
|
||||
"refresh": 1,
|
||||
"type": "query"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"uid": "prometheus"
|
||||
},
|
||||
"name": "no_refresh_with_options",
|
||||
"options": [],
|
||||
"refresh": 1,
|
||||
"type": "query"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"uid": "prometheus"
|
||||
},
|
||||
"name": "no_refresh_without_options",
|
||||
"options": [],
|
||||
"refresh": 1,
|
||||
"type": "query"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"uid": "prometheus"
|
||||
},
|
||||
"name": "unknown_refresh_with_options",
|
||||
"options": [],
|
||||
"refresh": 1,
|
||||
"type": "query"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"uid": "prometheus"
|
||||
},
|
||||
"name": "unknown_refresh_without_options",
|
||||
"options": [],
|
||||
"refresh": 1,
|
||||
"type": "query"
|
||||
},
|
||||
{
|
||||
"name": "custom_variable",
|
||||
"options": [
|
||||
{
|
||||
"text": "custom",
|
||||
"value": "custom"
|
||||
}
|
||||
],
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"name": "textbox_variable",
|
||||
"options": [
|
||||
{
|
||||
"text": "Hello",
|
||||
"value": "World"
|
||||
}
|
||||
],
|
||||
"type": "textbox"
|
||||
},
|
||||
{
|
||||
"name": "datasource_variable",
|
||||
"options": [
|
||||
{
|
||||
"text": "ds",
|
||||
"value": "ds"
|
||||
}
|
||||
],
|
||||
"type": "datasource"
|
||||
},
|
||||
{
|
||||
"name": "interval_variable",
|
||||
"options": [
|
||||
{
|
||||
"text": "1m",
|
||||
"value": "1m"
|
||||
}
|
||||
],
|
||||
"type": "interval"
|
||||
}
|
||||
]
|
||||
},
|
||||
"time": {
|
||||
"from": "now-6h",
|
||||
"to": "now"
|
||||
},
|
||||
"timepicker": {
|
||||
"refresh_intervals": [
|
||||
"5s",
|
||||
"10s",
|
||||
"30s",
|
||||
"1m",
|
||||
"5m",
|
||||
"15m",
|
||||
"30m",
|
||||
"1h",
|
||||
"2h",
|
||||
"1d"
|
||||
]
|
||||
},
|
||||
"title": "V29 Query Variables Refresh and Options Migration Test Dashboard"
|
||||
}
|
||||
@@ -1043,7 +1043,7 @@ describe('DashboardModel', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should have 11 variables after migration', () => {
|
||||
it('should have 14 variables after migration', () => {
|
||||
expect(model.templating.list.length).toBe(14);
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user