b232a812ab
* migrate to v19 * migrate to v18 * Migration to be verified: v17 Convert minSpan to maxPerRow in panels * Migration to be verified: 16 Grid layout migration * Refactor v17 and v19 migrations to use shared helper functions * Migration to be verified: 15 No-op migration for schema consistency * Migration to be verified: 14 Shared crosshair to graph tooltip migration * cleanup * wip * complete migration * fix lint issues * refactor and test with minimal graph config * update tests * migrate to v12 * extract defaults outside the func * lint * lint * add missing showValues prop * migrate to v11 * migrate to v10 * add test files * update * migrate to v9 * migrate to v8 * add context and fix latest version * add context * add context * generate snapshots * v13 should be no-op * clean up * fix tests * add context * snapshots * generate snapshots * update * snapshots * fix test * remove v28 * remove singlestat migraiton from frontend migrator because this is an automigration * remove unused function * Remove v24 table plugin logic * cleanup * remove plugin version for automigrate as it was used only in v24 and v28 that have been removed * cleanup * update snapshot * update snapshot * update snapshot --------- Co-authored-by: Dominik Prokop <dominik.prokop@grafana.com>
207 lines
5.5 KiB
Go
207 lines
5.5 KiB
Go
package schemaversion
|
|
|
|
import "context"
|
|
|
|
// V8 migration updates old InfluxDB query schema to the new format.
|
|
// This migration transforms the legacy InfluxDB query structure with fields, tags, and groupBy
|
|
// into the newer select-based query format.
|
|
//
|
|
// Background:
|
|
// In earlier versions, InfluxDB queries were stored using a different schema with separate
|
|
// fields, tags, and groupBy properties. This migration converts them to the newer select
|
|
// format that is more structured and easier to work with.
|
|
//
|
|
// The migration handles two cases:
|
|
// 1. Raw queries: Simply removes the fields and fill properties
|
|
// 2. Structured queries: Converts fields to select format and updates groupBy structure
|
|
//
|
|
// Example before migration (structured query):
|
|
// {
|
|
// "schemaVersion": 7,
|
|
// "panels": [
|
|
// {
|
|
// "targets": [
|
|
// {
|
|
// "fields": [
|
|
// {"name": "value", "func": "mean", "mathExpr": "*2", "asExpr": "doubled"}
|
|
// ],
|
|
// "tags": [{"key": "host", "value": "server1"}],
|
|
// "groupBy": [
|
|
// {"type": "time", "interval": "1m"},
|
|
// {"type": "tag", "key": "host"}
|
|
// ],
|
|
// "fill": "null"
|
|
// }
|
|
// ]
|
|
// }
|
|
// ]
|
|
// }
|
|
//
|
|
// Example after migration (structured query):
|
|
// {
|
|
// "schemaVersion": 8,
|
|
// "panels": [
|
|
// {
|
|
// "targets": [
|
|
// {
|
|
// "select": [
|
|
// [
|
|
// {"type": "field", "params": ["value"]},
|
|
// {"type": "mean", "params": []},
|
|
// {"type": "math", "params": ["*2"]},
|
|
// {"type": "alias", "params": ["doubled"]}
|
|
// ]
|
|
// ],
|
|
// "tags": [{"key": "host", "value": "server1"}],
|
|
// "groupBy": [
|
|
// {"type": "time", "params": ["1m"]},
|
|
// {"type": "tag", "params": ["host"]},
|
|
// {"type": "fill", "params": ["null"]}
|
|
// ]
|
|
// }
|
|
// ]
|
|
// }
|
|
// ]
|
|
// }
|
|
|
|
func V8(_ context.Context, dashboard map[string]interface{}) error {
|
|
dashboard["schemaVersion"] = 8
|
|
|
|
panels, ok := dashboard["panels"].([]interface{})
|
|
if !ok {
|
|
return nil
|
|
}
|
|
|
|
for _, p := range panels {
|
|
panel, ok := p.(map[string]interface{})
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
targets, ok := panel["targets"].([]interface{})
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
for _, t := range targets {
|
|
target, ok := t.(map[string]interface{})
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
// Check if this target has the old InfluxDB schema
|
|
fields, hasFields := target["fields"]
|
|
_, hasTags := target["tags"]
|
|
groupBy, hasGroupBy := target["groupBy"]
|
|
|
|
if !hasFields || !hasTags || !hasGroupBy {
|
|
continue
|
|
}
|
|
|
|
// Check if this is a raw query
|
|
rawQuery, isRawQuery := target["rawQuery"].(bool)
|
|
if isRawQuery && rawQuery {
|
|
// For raw queries, just delete fields and fill
|
|
delete(target, "fields")
|
|
delete(target, "fill")
|
|
} else {
|
|
// For structured queries, convert fields to select format
|
|
fieldsArray, ok := fields.([]interface{})
|
|
if ok {
|
|
selectArray := make([]interface{}, 0, len(fieldsArray))
|
|
|
|
for _, f := range fieldsArray {
|
|
field, ok := f.(map[string]interface{})
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
parts := make([]interface{}, 0)
|
|
|
|
// Add field part
|
|
if name, ok := field["name"].(string); ok {
|
|
parts = append(parts, map[string]interface{}{
|
|
"type": "field",
|
|
"params": []interface{}{name},
|
|
})
|
|
}
|
|
|
|
// Add function part
|
|
if funcName, ok := field["func"].(string); ok {
|
|
parts = append(parts, map[string]interface{}{
|
|
"type": funcName,
|
|
"params": []interface{}{},
|
|
})
|
|
}
|
|
|
|
// Add math expression if present
|
|
if mathExpr, ok := field["mathExpr"].(string); ok {
|
|
parts = append(parts, map[string]interface{}{
|
|
"type": "math",
|
|
"params": []interface{}{mathExpr},
|
|
})
|
|
}
|
|
|
|
// Add alias if present
|
|
if asExpr, ok := field["asExpr"].(string); ok {
|
|
parts = append(parts, map[string]interface{}{
|
|
"type": "alias",
|
|
"params": []interface{}{asExpr},
|
|
})
|
|
}
|
|
|
|
if len(parts) > 0 {
|
|
selectArray = append(selectArray, parts)
|
|
}
|
|
}
|
|
|
|
target["select"] = selectArray
|
|
}
|
|
|
|
// Remove the old fields property
|
|
delete(target, "fields")
|
|
|
|
// Update groupBy format
|
|
if groupByArray, ok := groupBy.([]interface{}); ok {
|
|
for _, gb := range groupByArray {
|
|
groupByPart, ok := gb.(map[string]interface{})
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
// Convert time groupBy
|
|
if partType, ok := groupByPart["type"].(string); ok && partType == "time" {
|
|
if interval, ok := groupByPart["interval"].(string); ok {
|
|
groupByPart["params"] = []interface{}{interval}
|
|
delete(groupByPart, "interval")
|
|
}
|
|
}
|
|
|
|
// Convert tag groupBy
|
|
if partType, ok := groupByPart["type"].(string); ok && partType == "tag" {
|
|
if key, ok := groupByPart["key"].(string); ok {
|
|
groupByPart["params"] = []interface{}{key}
|
|
delete(groupByPart, "key")
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add fill to groupBy if present
|
|
if fill, hasFill := target["fill"]; hasFill {
|
|
newGroupByArray := make([]interface{}, len(groupByArray))
|
|
copy(newGroupByArray, groupByArray)
|
|
newGroupByArray = append(newGroupByArray, map[string]interface{}{
|
|
"type": "fill",
|
|
"params": []interface{}{fill},
|
|
})
|
|
target["groupBy"] = newGroupByArray
|
|
delete(target, "fill")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|