Dashboard Migrations: V13 no-op, remove 28 and v24 as they are autoMigrations; and remove dead code from DashboardMigrator (#110008)

* 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

* extract defaults outside the func

* lint

* lint

* add missing showValues prop

* add context and fix latest version

* generate snapshots

* v13 should be no-op

* clean up

* 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

---------

Co-authored-by: Dominik Prokop <dominik.prokop@grafana.com>
This commit is contained in:
Haris Rozajac
2025-10-09 11:14:02 -06:00
committed by GitHub
co-authored by Dominik Prokop
parent 5eb295d850
commit 4d3c5d1550
45 changed files with 3555 additions and 6766 deletions
@@ -549,6 +549,7 @@ func cleanupPanelForSaveWithContext(panel map[string]interface{}, isNested bool)
// Clean up internal markers
delete(panel, "_originallyHadTransformations")
delete(panel, "_originallyHadFieldConfigCustom")
}
// filterDefaultValues removes properties that match the default values (matches frontend's isEqual logic)
@@ -600,8 +601,21 @@ func filterDefaultValues(panel map[string]interface{}, originalProperties map[st
for prop, defaultValue := range defaults {
if panelValue, exists := panel[prop]; exists {
if isEqual(panelValue, defaultValue) {
// Special case: fieldConfig is always removed if it matches defaults (frontend getSaveModel behavior)
// Special case: fieldConfig - handle preservation of original custom object first
if prop == "fieldConfig" {
// Check if we need to preserve the custom object before removing fieldConfig
if panel["_originallyHadFieldConfigCustom"] == true {
// Ensure fieldConfig structure exists with custom object
if fieldConfig, ok := panelValue.(map[string]interface{}); ok {
if defaults, ok := fieldConfig["defaults"].(map[string]interface{}); ok {
if _, hasCustom := defaults["custom"]; !hasCustom {
defaults["custom"] = map[string]interface{}{}
}
// Don't remove fieldConfig if we added custom back
continue
}
}
}
delete(panel, prop)
} else {
// Only remove if it wasn't originally present in the input
@@ -616,12 +630,32 @@ func filterDefaultValues(panel map[string]interface{}, originalProperties map[st
// Remove empty targets arrays (frontend removes them in cleanup)
removeIfDefaultValue(panel, "targets", []interface{}{})
// Handle case where fieldConfig was removed but originally had custom object
if panel["_originallyHadFieldConfigCustom"] == true {
if _, hasFieldConfig := panel["fieldConfig"]; !hasFieldConfig {
// Recreate fieldConfig with custom object
panel["fieldConfig"] = map[string]interface{}{
"defaults": map[string]interface{}{
"custom": map[string]interface{}{},
},
"overrides": []interface{}{},
}
}
}
// Clean up fieldConfig to match frontend behavior
if fieldConfig, exists := panel["fieldConfig"].(map[string]interface{}); exists {
// Clean up fieldConfig defaults to match frontend behavior
if defaults, hasDefaults := fieldConfig["defaults"].(map[string]interface{}); hasDefaults {
// Remove properties that frontend considers as defaults and omits
cleanupFieldConfigDefaults(defaults, panel)
// Preserve custom object if it was originally present, even if empty
if panel["_originallyHadFieldConfigCustom"] == true {
if _, hasCustom := defaults["custom"]; !hasCustom {
defaults["custom"] = map[string]interface{}{}
}
}
}
}
}
@@ -1085,3 +1119,36 @@ func trackPanelOriginalTransformations(panel map[string]interface{}) {
}
}
}
// trackOriginalFieldConfigCustom marks panels that had fieldConfig.defaults.custom in the original input
// This is needed to match frontend hasOwnProperty behavior and preserve empty custom objects
func trackOriginalFieldConfigCustom(dashboard map[string]interface{}) {
if panels, ok := dashboard["panels"].([]interface{}); ok {
for _, panelInterface := range panels {
if panel, ok := panelInterface.(map[string]interface{}); ok {
trackPanelOriginalFieldConfigCustom(panel)
}
}
}
}
// trackPanelOriginalFieldConfigCustom recursively tracks fieldConfig.defaults.custom in panels and nested panels
func trackPanelOriginalFieldConfigCustom(panel map[string]interface{}) {
// Mark if this panel had fieldConfig.defaults.custom in original input
if fieldConfig, ok := panel["fieldConfig"].(map[string]interface{}); ok {
if defaults, ok := fieldConfig["defaults"].(map[string]interface{}); ok {
if _, hasCustom := defaults["custom"]; hasCustom {
panel["_originallyHadFieldConfigCustom"] = true
}
}
}
// Handle nested panels in row panels
if nestedPanels, ok := panel["panels"].([]interface{}); ok {
for _, nestedPanelInterface := range nestedPanels {
if nestedPanel, ok := nestedPanelInterface.(map[string]interface{}); ok {
trackPanelOriginalFieldConfigCustom(nestedPanel)
}
}
}
}
@@ -807,6 +807,193 @@ func TestTransformationsArrayContextAwareLogic(t *testing.T) {
}
}
func TestTrackOriginalFieldConfigCustom(t *testing.T) {
tests := []struct {
name string
input map[string]interface{}
expected map[string]interface{}
}{
{
name: "track_top_level_panel_with_fieldConfig_custom",
input: map[string]interface{}{
"panels": []interface{}{
map[string]interface{}{
"id": 1,
"type": "timeseries",
"fieldConfig": map[string]interface{}{
"defaults": map[string]interface{}{
"custom": map[string]interface{}{},
},
"overrides": []interface{}{},
},
},
map[string]interface{}{
"id": 2,
"type": "table",
"fieldConfig": map[string]interface{}{
"defaults": map[string]interface{}{},
"overrides": []interface{}{},
},
},
map[string]interface{}{
"id": 3,
"type": "stat",
"title": "Panel without fieldConfig",
},
},
},
expected: map[string]interface{}{
"panels": []interface{}{
map[string]interface{}{
"id": 1,
"type": "timeseries",
"fieldConfig": map[string]interface{}{
"defaults": map[string]interface{}{
"custom": map[string]interface{}{},
},
"overrides": []interface{}{},
},
"_originallyHadFieldConfigCustom": true, // marker added
},
map[string]interface{}{
"id": 2,
"type": "table",
"fieldConfig": map[string]interface{}{
"defaults": map[string]interface{}{},
"overrides": []interface{}{},
},
// no marker added - no custom object
},
map[string]interface{}{
"id": 3,
"type": "stat",
"title": "Panel without fieldConfig",
// no marker added - no fieldConfig
},
},
},
},
{
name: "track_nested_panels_in_row_with_fieldConfig_custom",
input: map[string]interface{}{
"panels": []interface{}{
map[string]interface{}{
"id": 1,
"type": "row",
"title": "Row Panel",
"panels": []interface{}{
map[string]interface{}{
"id": 10,
"type": "singlestat",
"fieldConfig": map[string]interface{}{
"defaults": map[string]interface{}{
"custom": map[string]interface{}{},
},
"overrides": []interface{}{},
},
},
map[string]interface{}{
"id": 11,
"type": "graph",
"fieldConfig": map[string]interface{}{
"defaults": map[string]interface{}{
"unit": "bytes",
},
"overrides": []interface{}{},
},
},
},
},
},
},
expected: map[string]interface{}{
"panels": []interface{}{
map[string]interface{}{
"id": 1,
"type": "row",
"title": "Row Panel",
"panels": []interface{}{
map[string]interface{}{
"id": 10,
"type": "singlestat",
"fieldConfig": map[string]interface{}{
"defaults": map[string]interface{}{
"custom": map[string]interface{}{},
},
"overrides": []interface{}{},
},
"_originallyHadFieldConfigCustom": true, // marker added to nested panel
},
map[string]interface{}{
"id": 11,
"type": "graph",
"fieldConfig": map[string]interface{}{
"defaults": map[string]interface{}{
"unit": "bytes",
},
"overrides": []interface{}{},
},
// no marker added - no custom object
},
},
},
},
},
},
{
name: "track_panels_with_non_empty_custom",
input: map[string]interface{}{
"panels": []interface{}{
map[string]interface{}{
"id": 1,
"type": "timeseries",
"fieldConfig": map[string]interface{}{
"defaults": map[string]interface{}{
"custom": map[string]interface{}{
"axisPlacement": "left",
},
},
"overrides": []interface{}{},
},
},
},
},
expected: map[string]interface{}{
"panels": []interface{}{
map[string]interface{}{
"id": 1,
"type": "timeseries",
"fieldConfig": map[string]interface{}{
"defaults": map[string]interface{}{
"custom": map[string]interface{}{
"axisPlacement": "left",
},
},
"overrides": []interface{}{},
},
"_originallyHadFieldConfigCustom": true, // marker added for non-empty custom
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Create a deep copy of input for testing
dashboard := deepCopy(tt.input).(map[string]interface{})
// Apply the tracking logic
trackOriginalFieldConfigCustom(dashboard)
// Verify the result
if !compareValues(dashboard, tt.expected) {
t.Errorf("Test %s failed.\nExpected: %+v\nGot: %+v", tt.name, tt.expected, dashboard)
}
})
}
}
func TestTrackOriginalTransformations(t *testing.T) {
tests := []struct {
name string
@@ -915,6 +1102,123 @@ func TestTrackOriginalTransformations(t *testing.T) {
}
}
func TestCleanupPanelForSavePreservesOriginalFieldConfigCustom(t *testing.T) {
tests := []struct {
name string
input map[string]interface{}
expected map[string]interface{}
}{
{
name: "preserve_empty_custom_when_originally_present",
input: map[string]interface{}{
"type": "singlestat",
"fieldConfig": map[string]interface{}{
"defaults": map[string]interface{}{
"custom": map[string]interface{}{}, // Empty but originally present
},
"overrides": []interface{}{},
},
"_originallyHadFieldConfigCustom": true, // Marker indicating it was originally present
},
expected: map[string]interface{}{
"type": "stat", // Auto-migrated from singlestat
"autoMigrateFrom": "singlestat", // Auto-migration marker
"fieldConfig": map[string]interface{}{
"defaults": map[string]interface{}{
"custom": map[string]interface{}{}, // Should be preserved
},
"overrides": []interface{}{},
},
},
},
{
name: "remove_empty_custom_when_not_originally_present",
input: map[string]interface{}{
"type": "timeseries",
"fieldConfig": map[string]interface{}{
"defaults": map[string]interface{}{
"custom": map[string]interface{}{}, // Empty and not originally present
},
"overrides": []interface{}{},
},
// No marker - custom was not originally present
},
expected: map[string]interface{}{
"type": "timeseries",
// fieldConfig should be removed entirely as it matches defaults
},
},
{
name: "preserve_non_empty_custom_regardless_of_marker",
input: map[string]interface{}{
"type": "table",
"fieldConfig": map[string]interface{}{
"defaults": map[string]interface{}{
"custom": map[string]interface{}{
"displayMode": "list",
},
},
"overrides": []interface{}{},
},
},
expected: map[string]interface{}{
"type": "table",
"fieldConfig": map[string]interface{}{
"defaults": map[string]interface{}{
"custom": map[string]interface{}{
"displayMode": "list",
},
},
"overrides": []interface{}{},
},
},
},
{
name: "add_custom_when_originally_present_but_missing",
input: map[string]interface{}{
"type": "stat",
"fieldConfig": map[string]interface{}{
"defaults": map[string]interface{}{}, // custom missing
"overrides": []interface{}{},
},
"_originallyHadFieldConfigCustom": true, // But marker indicates it was originally present
},
expected: map[string]interface{}{
"type": "stat",
"fieldConfig": map[string]interface{}{
"defaults": map[string]interface{}{
"custom": map[string]interface{}{}, // Should be added back
},
"overrides": []interface{}{},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
panel := make(map[string]interface{})
for k, v := range tt.input {
panel[k] = v
}
cleanupPanelForSaveWithContext(panel, false)
// Verify expected properties exist
for key, expectedValue := range tt.expected {
if actualValue, exists := panel[key]; !exists {
t.Errorf("Property %s should exist but is missing", key)
} else if !compareValues(actualValue, expectedValue) {
t.Errorf("Property %s has wrong value. Expected: %v, Got: %v", key, expectedValue, actualValue)
}
}
// Verify internal markers are cleaned up
assertPropertyRemoved(t, panel, "_originallyHadFieldConfigCustom")
})
}
}
func TestCleanupPanelForSave(t *testing.T) {
tests := []struct {
name string
+4
View File
@@ -66,6 +66,10 @@ func (m *migrator) migrate(ctx context.Context, dash map[string]interface{}, tar
// This is needed to match frontend hasOwnProperty behavior
trackOriginalTransformations(dash)
// 1.1. Track which panels had fieldConfig.defaults.custom in original input
// This is needed to preserve empty custom objects that were originally present
trackOriginalFieldConfigCustom(dash)
// 2. Apply ALL frontend defaults FIRST (DashboardModel + PanelModel defaults)
// This replicates the behavior of the frontend DashboardModel and PanelModel constructors
applyFrontendDefaults(dash)
@@ -1,89 +0,0 @@
package schemaversion
var notPersistedProperties = []string{
"events",
"isViewing",
"isEditing",
"isInView",
"hasRefreshed",
"cachedPluginOptions",
"plugin",
"queryRunner",
"replaceVariables",
"configRev",
"hasSavedPanelEditChange",
"getDisplayTitle",
"dataSupport",
"key",
"isNew",
"refreshWhenInView",
}
var mustKeepProperties = []string{
"id",
"gridPos",
"type",
"title",
"scopedVars",
"repeat",
"repeatPanelId",
"repeatDirection",
"repeatedByRow",
"minSpan",
"collapsed",
"panels",
"targets",
"datasource",
"timeFrom",
"timeShift",
"hideTimeOverride",
"description",
"links",
"fullscreen",
"isEditing",
"isViewing",
"hasRefreshed",
"events",
"cacheTimeout",
"queryCachingTTL",
"cachedPluginOptions",
"transparent",
"pluginVersion",
"queryRunner",
"transformations",
"fieldConfig",
"maxDataPoints",
"interval",
"replaceVariables",
"libraryPanel",
"getDisplayTitle",
"configRev",
"key",
}
// getOptionsToRemember returns a map of panel properties that should be remembered
// during panel type changes, excluding notPersistedProperties and mustKeepProperties
func getOptionsToRemember(panel map[string]interface{}) map[string]interface{} {
// Create sets for faster lookup
notPersistedSet := make(map[string]bool)
for _, prop := range notPersistedProperties {
notPersistedSet[prop] = true
}
mustKeepSet := make(map[string]bool)
for _, prop := range mustKeepProperties {
mustKeepSet[prop] = true
}
// Filter the panel properties
result := make(map[string]interface{})
for key, value := range panel {
// Skip properties that are in notPersistedProperties or mustKeepProperties
if notPersistedSet[key] || mustKeepSet[key] {
continue
}
result[key] = value
}
return result
}
@@ -7,11 +7,8 @@ import (
)
const (
MIN_VERSION = 13
MIN_VERSION = 12
LATEST_VERSION = 42
// The pluginVersion to set after simulating auto-migrate for angular panels
pluginVersionForAutoMigrate = "12.1.0"
)
type SchemaVersionMigrationFunc func(context.Context, map[string]interface{}) error
@@ -38,6 +35,7 @@ type PanelPluginInfo struct {
func GetMigrations(dsInfoProvider DataSourceInfoProvider) map[int]SchemaVersionMigrationFunc {
return map[int]SchemaVersionMigrationFunc{
13: V13,
14: V14,
15: V15,
16: V16,
@@ -0,0 +1,11 @@
package schemaversion
import (
"context"
)
// V13 is a no-op migration
func V13(_ context.Context, dashboard map[string]interface{}) error {
dashboard["schemaVersion"] = 13
return nil
}
@@ -0,0 +1,38 @@
package schemaversion_test
import (
"testing"
"github.com/grafana/grafana/apps/dashboard/pkg/migration/schemaversion"
)
func TestV13(t *testing.T) {
tests := []migrationTestCase{
{
name: "v13 no-op migration, updates schema version only",
input: map[string]interface{}{
"title": "V13 No-Op Migration Test Dashboard",
"schemaVersion": 12,
"panels": []interface{}{
map[string]interface{}{
"type": "graph",
"title": "Panel remains unchanged",
"id": 1,
},
},
},
expected: map[string]interface{}{
"title": "V13 No-Op Migration Test Dashboard",
"schemaVersion": 13,
"panels": []interface{}{
map[string]interface{}{
"type": "graph",
"title": "Panel remains unchanged",
"id": 1,
},
},
},
},
}
runMigrationTests(t, tests, schemaversion.V13)
}
+54 -572
View File
@@ -2,189 +2,59 @@ package schemaversion
import (
"context"
"strconv"
)
// V24 migration migrates the angular table panel to the standard table panel
// In the frontend, this is an auto-migration meaning that this angular panel is always migrated to table panel.
// The backend replicates the complete frontend auto-migration logic since it cannot rely on frontend auto-migration.
//
// This migration performs:
// 1. Converts 'styles' array to 'fieldConfig' with 'defaults' and 'overrides'
// 2. Migrates thresholds and colors to new threshold format
// 3. Converts column-specific styles to field overrides
// 4. Migrates transformations from old format to new transformation system
// 5. Handles various style properties: unit, decimals, alignment, color modes, links, date formatting, hidden columns
// 6. Removes deprecated properties: styles, transform, columns
// V24 migration handles setting autoMigrateFrom
// This is a hacky way that matches frontend's logic
// For reason see https://github.com/grafana/grafana/pull/102146
// The issue is that if panel is "table" and it has styles, it should be migrated to "table-old"
// Example 1: Basic table with defaults
// Before migration:
// Example before migration:
// {
// "panels": [
// {
// "id": 1,
// "type": "table",
// "title": "Basic Table",
// "styles": [
// {
// "pattern": "/.*/",
// "thresholds": ["10", "20", "30"],
// "colors": ["green", "yellow", "red"],
// "unit": "bytes",
// "decimals": 2
// }
// ],
// "targets": [{ "refId": "A" }]
// }
// ]
// "id": 4,
// "type": "table",
// "title": "Table with Timeseries to Rows Transform",
// "description": "Tests migration of timeseries_to_rows transform to seriesToRows transformation.",
// "styles": [
// {
// "pattern": "/.*/",
// "unit": "short"
// }
// ],
// "transform": "timeseries_to_rows",
// "targets": [{ "refId": "A" }]
// }
//
// After migration:
// Example after migration:
// {
// "panels": [
// {
// "id": 1,
// "type": "table",
// "title": "Basic Table",
// "fieldConfig": {
// "defaults": {
// "unit": "bytes",
// "decimals": 2,
// "custom": {},
// "thresholds": {
// "mode": "absolute",
// "steps": [
// { "color": "green", "value": null },
// { "color": "green", "value": 10 },
// { "color": "yellow", "value": 20 },
// { "color": "red", "value": 30 }
// ]
// }
// },
// "overrides": []
// },
// "transformations": [],
// "targets": [{ "refId": "A" }],
// "pluginVersion": "{current_grafana_version}"
// }
// ]
// }
// Example 2: Complex table with overrides and transformations
// Before migration:
// {
// "panels": [
// {
// "id": 2,
// "type": "table",
// "title": "Complex Table",
// "styles": [
// {
// "pattern": "/.*/",
// "unit": "percent",
// "align": "center",
// "colorMode": "cell"
// },
// {
// "pattern": "Status",
// "alias": "Current Status",
// "colorMode": "value",
// "align": "left"
// },
// {
// "pattern": "/Error.*/",
// "link": true,
// "linkUrl": "http://example.com/errors",
// "linkTooltip": "View errors",
// "linkTargetBlank": true
// },
// {
// "pattern": "Time",
// "type": "date",
// "dateFormat": "YYYY-MM-DD HH:mm:ss",
// "alias": "Timestamp"
// },
// {
// "pattern": "Hidden",
// "type": "hidden"
// }
// ],
// "transform": "timeseries_aggregations",
// "columns": [
// { "value": "avg", "text": "Average" },
// { "value": "max", "text": "Maximum" }
// ],
// "targets": [{ "refId": "A" }]
// }
// ]
// }
//
// After migration:
// {
// "panels": [
// {
// "id": 2,
// "type": "table",
// "title": "Complex Table",
// "fieldConfig": {
// "defaults": {
// "unit": "percent",
// "custom": {
// "align": "center",
// "cellOptions": { "type": "color-background" }
// }
// },
// "overrides": [
// {
// "matcher": { "id": "byName", "options": "Status" },
// "properties": [
// { "id": "displayName", "value": "Current Status" },
// { "id": "custom.cellOptions", "value": { "type": "color-text" } },
// { "id": "custom.align", "value": "left" }
// ]
// },
// {
// "matcher": { "id": "byRegexp", "options": "/Error.*/" },
// "properties": [
// {
// "id": "links",
// "value": [{
// "title": "View errors",
// "url": "http://example.com/errors",
// "targetBlank": true
// }]
// }
// ]
// },
// {
// "matcher": { "id": "byName", "options": "Time" },
// "properties": [
// { "id": "displayName", "value": "Timestamp" },
// { "id": "unit", "value": "time: YYYY-MM-DD HH:mm:ss" }
// ]
// },
// {
// "matcher": { "id": "byName", "options": "Hidden" },
// "properties": [
// { "id": "custom.hideFrom.viz", "value": true }
// ]
// }
// ]
// },
// "transformations": [
// {
// "id": "reduce",
// "options": {
// "reducers": ["mean", "max"],
// "includeTimeField": false
// }
// }
// ],
// "targets": [{ "refId": "A" }],
// "pluginVersion": "{current_grafana_version}"
// }
// ]
// }
// "autoMigrateFrom": "table-old",
// "datasource": {
// "apiVersion": "v1",
// "type": "prometheus",
// "uid": "default-ds-uid"
// },
// "description": "Tests migration of timeseries_to_rows transform to seriesToRows transformation.",
// "id": 4,
// "styles": [
// {
// "pattern": "/.*/",
// "unit": "short"
// }
// ],
// "targets": [
// {
// "datasource": {
// "apiVersion": "v1",
// "type": "prometheus",
// "uid": "default-ds-uid"
// },
// "refId": "A"
// }
// ],
// "title": "Table with Timeseries to Rows Transform",
// "transform": "timeseries_to_rows",
// "type": "table"
// }
func V24(_ context.Context, dashboard map[string]interface{}) error {
dashboard["schemaVersion"] = 24
@@ -211,406 +81,18 @@ func V24(_ context.Context, dashboard map[string]interface{}) error {
continue
}
// The grafana version that matches the hardcoded autoMigrate plugins
panelMap["pluginVersion"] = pluginVersionForAutoMigrate
err := tablePanelChangedHandler(panelMap)
if err != nil {
return err
}
}
return nil
}
func tablePanelChangedHandler(panel map[string]interface{}) error {
prevOptions := getOptionsToRemember(panel)
transformations := migrateTransformations(panel, prevOptions)
prevDefaults := findDefaultStyle(prevOptions)
defaults := migrateDefaults(prevDefaults)
overrides := findNonDefaultStyles(prevOptions)
if len(overrides) == 0 {
overrides = []interface{}{}
}
// Only add transformations if they're not empty - frontend omits empty arrays
if len(transformations) > 0 {
panel["transformations"] = transformations
}
panel["fieldConfig"] = map[string]interface{}{
"defaults": defaults,
"overrides": overrides,
}
// Add minimal table panel options to match frontend behavior
// Frontend doesn't add default footer options, so we don't either
panel["options"] = map[string]interface{}{
"cellHeight": "sm",
"showHeader": true,
}
// Remove deprecated properties
delete(panel, "styles")
delete(panel, "transform")
delete(panel, "columns")
// Remove legend property - frontend table panel migration doesn't preserve it
delete(panel, "legend")
return nil
}
// findDefaultStyle finds the style with pattern '/.*/' (default style)
func findDefaultStyle(prevOptions map[string]interface{}) map[string]interface{} {
if styles, ok := prevOptions["styles"].([]interface{}); ok {
for _, style := range styles {
if styleMap, ok := style.(map[string]interface{}); ok {
if pattern, ok := styleMap["pattern"].(string); ok && pattern == "/.*/" {
return styleMap
}
}
}
}
return nil
}
// findNonDefaultStyles finds all styles that don't have pattern '/.*/'
func findNonDefaultStyles(prevOptions map[string]interface{}) []interface{} {
var overrides []interface{}
if styles, ok := prevOptions["styles"].([]interface{}); ok {
for _, style := range styles {
if styleMap, ok := style.(map[string]interface{}); ok {
if pattern, ok := styleMap["pattern"].(string); ok && pattern != "/.*/" {
override := migrateTableStyleToOverride(styleMap)
overrides = append(overrides, override)
}
}
}
}
return overrides
}
// migrateTransformations converts old table transformations to new format
func migrateTransformations(panel map[string]interface{}, oldOpts map[string]interface{}) []interface{} {
transformations := []interface{}{}
if existing, ok := panel["transformations"].([]interface{}); ok {
transformations = existing
}
// Check if oldOpts has a transform that we can map
if transform, ok := oldOpts["transform"].(string); ok {
if newTransformID, exists := transformsMap[transform]; exists {
opts := map[string]interface{}{
"reducers": []interface{}{},
}
// Handle timeseries_aggregations specifically
if transform == "timeseries_aggregations" {
opts["includeTimeField"] = false
// Map columns to reducers
if columns, ok := oldOpts["columns"].([]interface{}); ok {
var reducers []interface{}
for _, column := range columns {
if columnMap, ok := column.(map[string]interface{}); ok {
if value, ok := columnMap["value"].(string); ok {
if reducer, exists := columnsMap[value]; exists {
reducers = append(reducers, reducer)
}
}
}
}
opts["reducers"] = reducers
}
}
// Add the transformation
transformation := map[string]interface{}{
"id": newTransformID,
"options": opts,
}
transformations = append(transformations, transformation)
}
}
return transformations
}
// transformsMap maps old transform names to new transformation IDs
var transformsMap = map[string]string{
"timeseries_to_rows": "seriesToRows",
"timeseries_to_columns": "seriesToColumns",
"timeseries_aggregations": "reduce",
"table": "merge",
}
// columnsMap maps old column values to new reducer names
var columnsMap = map[string]string{
"avg": "mean",
"min": "min",
"max": "max",
"total": "sum",
"current": "lastNotNull",
"count": "count",
}
// migrateTableStyleToOverride converts a table style to a field config override
func migrateTableStyleToOverride(style map[string]interface{}) map[string]interface{} {
pattern, _ := style["pattern"].(string)
// Determine field matcher ID based on pattern
fieldMatcherID := "byName"
if pattern != "" && len(pattern) >= 2 && pattern[0] == '/' && pattern[len(pattern)-1] == '/' {
fieldMatcherID = "byRegexp"
}
override := map[string]interface{}{
"matcher": map[string]interface{}{
"id": fieldMatcherID,
"options": pattern,
},
"properties": []interface{}{},
}
properties := override["properties"].([]interface{})
// Add display name
if alias, ok := style["alias"].(string); ok && alias != "" {
properties = append(properties, map[string]interface{}{
"id": "displayName",
"value": alias,
})
}
// Add unit
if unit, ok := style["unit"].(string); ok && unit != "" {
properties = append(properties, map[string]interface{}{
"id": "unit",
"value": unit,
})
}
// Add decimals
if decimals := GetIntValue(style, "decimals", -1); decimals != -1 {
properties = append(properties, map[string]interface{}{
"id": "decimals",
"value": decimals,
})
}
// Handle date type
if styleType, ok := style["type"].(string); ok && styleType == "date" {
if dateFormat, ok := style["dateFormat"].(string); ok {
properties = append(properties, map[string]interface{}{
"id": "unit",
"value": "time: " + dateFormat,
})
}
}
// Handle hidden type
if styleType, ok := style["type"].(string); ok && styleType == "hidden" {
properties = append(properties, map[string]interface{}{
"id": "custom.hideFrom.viz",
"value": true,
})
}
// Handle links
if link, ok := style["link"].(bool); ok && link {
linkTooltip, _ := style["linkTooltip"].(string)
linkUrl, _ := style["linkUrl"].(string)
linkTargetBlank, _ := style["linkTargetBlank"].(bool)
properties = append(properties, map[string]interface{}{
"id": "links",
"value": []interface{}{
map[string]interface{}{
"title": linkTooltip,
"url": linkUrl,
"targetBlank": linkTargetBlank,
},
},
})
}
// Handle color mode
if colorMode, ok := style["colorMode"].(string); ok && colorMode != "" {
if newColorMode, exists := colorModeMap[colorMode]; exists {
properties = append(properties, map[string]interface{}{
"id": "custom.cellOptions",
"value": map[string]interface{}{
"type": newColorMode,
},
})
}
}
// Handle alignment
if align, ok := style["align"].(string); ok && align != "" {
var alignValue interface{}
if align == "auto" {
alignValue = nil // Frontend sets to null and filters it out
var currentType string
if wasAngularTable {
currentType = "table-old"
} else {
alignValue = align
currentType = "table"
}
properties = append(properties, map[string]interface{}{
"id": "custom.align",
"value": alignValue,
})
}
// Handle thresholds
if thresholds, ok := style["thresholds"].([]interface{}); ok && len(thresholds) > 0 {
if colors, ok := style["colors"].([]interface{}); ok && len(colors) > 0 {
steps := generateThresholds(thresholds, colors)
properties = append(properties, map[string]interface{}{
"id": "thresholds",
"value": map[string]interface{}{
"mode": "absolute",
"steps": steps,
},
})
if currentType == "table-old" {
panelMap["autoMigrateFrom"] = "table-old"
panelMap["type"] = "table"
}
}
override["properties"] = properties
return override
}
// migrateDefaults converts default table styles to field config defaults
func migrateDefaults(prevDefaults map[string]interface{}) map[string]interface{} {
defaults := map[string]interface{}{
"custom": map[string]interface{}{
"align": "auto",
"cellOptions": map[string]interface{}{
"type": "auto",
},
"inspect": false,
"footer": map[string]interface{}{
"reducers": []interface{}{},
},
},
"mappings": []interface{}{},
}
// Add default thresholds for all table panels to match frontend behavior
// The frontend applies the table panel's default field config which includes thresholds
hasThresholds := false
if prevDefaults != nil {
if thresholds, ok := prevDefaults["thresholds"].([]interface{}); ok && len(thresholds) > 0 {
hasThresholds = true
}
}
// Add default thresholds for all table panels (when prevDefaults exists) without existing thresholds
if !hasThresholds {
defaults["thresholds"] = map[string]interface{}{
"mode": "absolute",
"steps": []interface{}{
map[string]interface{}{"color": "green", "value": (*float64)(nil)},
map[string]interface{}{"color": "red", "value": 80},
},
}
}
if prevDefaults == nil {
return defaults
}
if unit := GetStringValue(prevDefaults, "unit"); unit != "" {
defaults["unit"] = unit
}
if decimals := GetIntValue(prevDefaults, "decimals", -1); decimals != -1 {
defaults["decimals"] = decimals
}
if alias, ok := prevDefaults["alias"].(string); ok {
defaults["displayName"] = alias
}
if align, ok := prevDefaults["align"].(string); ok && align != "" {
var alignValue interface{}
if align == "auto" {
alignValue = nil // Frontend sets to null and filters it out
} else {
alignValue = align
}
defaults["custom"].(map[string]interface{})["align"] = alignValue
}
if thresholds, ok := prevDefaults["thresholds"].([]interface{}); ok && len(thresholds) > 0 {
if colors, ok := prevDefaults["colors"].([]interface{}); ok && len(colors) > 0 {
steps := generateThresholds(thresholds, colors)
defaults["thresholds"] = map[string]interface{}{
"mode": "absolute",
"steps": steps,
}
}
}
if colorMode, ok := prevDefaults["colorMode"].(string); ok && colorMode != "" {
if newColorMode, exists := colorModeMap[colorMode]; exists {
defaults["custom"].(map[string]interface{})["cellOptions"] = map[string]interface{}{
"type": newColorMode,
}
}
}
return defaults
}
func generateThresholds(thresholds []interface{}, colors []interface{}) []interface{} {
steps := []interface{}{}
// Add the base step (equivalent to -Infinity)
var baseColor interface{} = "red" // default fallback
if len(colors) > 0 && colors[0] != nil {
baseColor = colors[0]
}
steps = append(steps, map[string]interface{}{
"color": baseColor,
"value": (*float64)(nil),
})
// Add threshold steps
for i, threshold := range thresholds {
var value float64
switch v := threshold.(type) {
case string:
if parsed, err := strconv.ParseFloat(v, 64); err == nil {
value = parsed
}
case float64:
value = v
case int:
value = float64(v)
}
step := map[string]interface{}{
"value": value,
}
// Only add color if there's a corresponding color in the colors array
// This matches the frontend behavior where colors[idx] might be undefined
if i+1 < len(colors) && colors[i+1] != nil {
step["color"] = colors[i+1]
}
steps = append(steps, step)
}
return steps
}
var colorModeMap = map[string]string{
"cell": "color-background",
"row": "color-background",
"value": "color-text",
return nil
}
@@ -44,6 +44,16 @@ func TestV24TablePanelMigration(t *testing.T) {
},
description: "V24 migration should not add empty transformations arrays to table panels",
},
{
name: "migrate_table_old_to_table",
input: map[string]interface{}{
"type": "table-old",
},
expected: map[string]interface{}{
"type": "table",
},
description: "V24 migration should migrate table-old to table",
},
}
for _, tt := range tests {
+13 -829
View File
@@ -2,61 +2,32 @@ package schemaversion
import (
"context"
"fmt"
"strconv"
"strings"
)
// V28 migrates singlestat panels to stat/gauge panels and removes deprecated variable properties.
//
// The migration performs two main tasks:
// 1. Migrates singlestat panels to either stat or gauge panels based on their configuration
// 2. Removes deprecated variable properties (tags, tagsQuery, tagValuesQuery, useTags)
//
// The migration includes comprehensive logic from the frontend:
// - Panel type migration (singlestat -> stat/gauge)
// - Field config migration with thresholds, mappings, and display options
// - Options migration including reduceOptions, orientation, and other panel-specific settings
// - Support for both angular singlestat and grafana-singlestat-panel migrations
// V28 removes deprecated variable properties (tags, tagsQuery, tagValuesQuery, useTags)
//
// Example before migration:
//
// "panels": [
// {
// "type": "singlestat",
// "gauge": { "show": true },
// "targets": [{ "refId": "A" }]
// }
// ],
// "templating": {
// "list": [
// { "name": "var1", "tags": ["tag1"], "tagsQuery": "query", "tagValuesQuery": "values", "useTags": true }
// ]
// {
// "templating": {
// "list": [
// { "name": "var1", "tags": ["tag1"], "tagsQuery": "query", "tagValuesQuery": "values", "useTags": true }
// ]
// }
// }
//
// Example after migration:
//
// "panels": [
// {
// "type": "gauge",
// "targets": [{ "refId": "A" }]
// }
// ],
// "templating": {
// "list": [
// { "name": "var1" }
// ]
// {
// "templating": {
// "list": [
// { "name": "var1" }
// ]
// }
// }
func V28(_ context.Context, dashboard map[string]interface{}) error {
dashboard["schemaVersion"] = 28
// Migrate singlestat panels
if panels, ok := dashboard["panels"].([]interface{}); ok {
if err := processPanels(panels); err != nil {
return err
}
}
// Remove deprecated variable properties
if templating, ok := dashboard["templating"].(map[string]interface{}); ok {
if list, ok := templating["list"].([]interface{}); ok {
@@ -71,751 +42,6 @@ func V28(_ context.Context, dashboard map[string]interface{}) error {
return nil
}
func processPanels(panels []interface{}) error {
for _, panel := range panels {
p, ok := panel.(map[string]interface{})
if !ok {
continue
}
// Process nested panels if this is a row panel
if p["type"] == "row" {
if nestedPanels, ok := p["panels"].([]interface{}); ok {
if err := processPanels(nestedPanels); err != nil {
return err
}
}
continue
}
// Migrate singlestat panels (including those already auto-migrated to stat)
if p["type"] == "singlestat" || p["type"] == "grafana-singlestat-panel" ||
p["autoMigrateFrom"] == "singlestat" || p["autoMigrateFrom"] == "grafana-singlestat-panel" {
if err := migrateSinglestatPanel(p); err != nil {
return err
}
}
// Note: Panel defaults (including options object) are already applied
// by applyPanelDefaults() in the main migration flow for ALL panels
// No need for stat-specific normalization
}
return nil
}
func migrateSinglestatPanel(panel map[string]interface{}) error {
targetType := "stat"
// NOTE: The legacy types "singlestat" and "gauge" are both angular only
// This are not supported by any version that could run this migration, so there is
// no need to maintain a distinction or fallback to the non-stat version
// NOTE: DashboardMigrator's migrateSinglestat function has some logic that never gets called
// migrateSinglestat will only run if (panel.type === 'singlestat')
// but this will not be the case because PanelModel runs restoreModel in the constructor
// and since singlestat is in the autoMigrateAngular map, it will be migrated to stat,
// and therefore migrateSinglestat will never run so this logic inside of it will never apply
// if ((panel as any).gauge?.show) {
// gaugePanelPlugin.meta = config.panels['gauge']
// panel.changePlugin(gaugePanelPlugin)
// Store original type for migration context (only for stat/gauge migration)
// Set autoMigrateFrom to track the original type for proper migration logic
originalType := panel["type"].(string)
// Only set autoMigrateFrom if it doesn't already exist (preserve frontend defaults)
if _, exists := panel["autoMigrateFrom"]; !exists {
panel["autoMigrateFrom"] = originalType
}
panel["type"] = targetType
panel["pluginVersion"] = pluginVersionForAutoMigrate
// Migrate panel options and field config
migrateSinglestatOptions(panel, originalType)
return nil
}
// migrateSinglestatOptions handles the complete migration of singlestat panel options and field config
func migrateSinglestatOptions(panel map[string]interface{}, originalType string) {
// Preserve important panel-level properties that should not be removed
// These properties are preserved by the frontend's getSaveModel() method
var maxDataPoints interface{}
if mdp, exists := panel["maxDataPoints"]; exists {
maxDataPoints = mdp
}
// Initialize field config if not present
if panel["fieldConfig"] == nil {
panel["fieldConfig"] = map[string]interface{}{
"defaults": map[string]interface{}{},
"overrides": []interface{}{},
}
}
fieldConfig := panel["fieldConfig"].(map[string]interface{})
defaults := fieldConfig["defaults"].(map[string]interface{})
// Migrate from angular singlestat configuration using appropriate strategy
// Use autoMigrateFrom if available, otherwise use originalType
migrationType := originalType
if autoMigrateFrom, exists := panel["autoMigrateFrom"].(string); exists {
migrationType = autoMigrateFrom
}
if migrationType == "grafana-singlestat-panel" {
migrateGrafanaSinglestatPanel(panel, defaults)
} else {
migratetSinglestat(panel, defaults)
}
// Apply shared migration logic
applySharedSinglestatMigration(defaults)
// Apply complete stat panel defaults (matches frontend getPanelOptionsWithDefaults)
// The frontend applies these defaults after migration via applyPluginOptionDefaults
applyCompleteStatPanelDefaults(panel)
// Create proper fieldConfig structure from defaults
createFieldConfigFromDefaults(panel, defaults)
// Restore preserved panel-level properties
if maxDataPoints != nil {
panel["maxDataPoints"] = maxDataPoints
}
// Clean up old angular properties after migration
cleanupAngularProperties(panel)
}
// getDefaultStatOptions returns the default options structure for stat panels
// This matches the frontend's stat panel defaultOptions exactly
func getDefaultStatOptions() map[string]interface{} {
// For now, return the explicit defaults until we integrate the centralized system
return map[string]interface{}{
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"percentChangeColorMode": "standard",
"showPercentChange": false,
"textMode": "auto",
"wideLayout": true,
"reduceOptions": map[string]interface{}{
"calcs": []string{"lastNotNull"}, // Matches frontend: ReducerID.lastNotNull
"fields": "",
"values": false,
},
"orientation": "auto",
}
}
// migratetSinglestat handles explicit migration from 'singlestat' panels
// Based on frontend migrateFromAngularSinglestat function
func migratetSinglestat(panel map[string]interface{}, defaults map[string]interface{}) {
angularOpts := extractAngularOptions(panel)
// Extract valueName for reducer mapping (matches frontend migrateFromAngularSinglestat)
var valueName string
if vn, ok := angularOpts["valueName"].(string); ok {
valueName = vn
}
// Set calcs based on valueName (matches frontend: calcs: [reducer ? reducer.id : ReducerID.mean])
var calcs []string
if reducer := getReducerForValueName(valueName); reducer != "" {
calcs = []string{reducer}
} else {
// Use mean as fallback (matches frontend migrateFromAngularSinglestat: ReducerID.mean)
calcs = []string{"mean"}
}
// Create options exactly like frontend migrateFromAngularSinglestat
options := map[string]interface{}{
"reduceOptions": map[string]interface{}{
"calcs": calcs,
"fields": "",
"values": false,
},
"orientation": "horizontal", // Matches frontend migrateFromAngularSinglestat: VizOrientation.Horizontal
}
// Migrate thresholds FIRST (consolidated: both panel types create DEFAULT_THRESHOLDS for empty strings)
migrateThresholds(angularOpts, defaults)
// If no thresholds were set from angular migration, add default stat panel thresholds
// This matches the behavior of frontend pluginLoaded which adds default thresholds
if _, hasThresholds := defaults["thresholds"]; !hasThresholds {
defaults["thresholds"] = map[string]interface{}{
"mode": "absolute",
"steps": []interface{}{
map[string]interface{}{
"color": "green",
"value": (*float64)(nil),
},
map[string]interface{}{
"color": "red",
"value": 80,
},
},
}
}
// Apply common angular option migrations (value mappings can now use threshold colors)
applyCommonAngularMigration(panel, defaults, options, angularOpts)
// Merge new options with existing panel options to preserve properties like maxDataPoints
if existingOptions, exists := panel["options"].(map[string]interface{}); exists {
for key, value := range options {
existingOptions[key] = value
}
} else {
panel["options"] = options
}
}
// migrateGrafanaSinglestatPanel handles auto-migration from 'grafana-singlestat-panel'
// Uses the same migration logic as singlestat panels since the frontend applies
// migrateFromAngularSinglestat to both panel types.
func migrateGrafanaSinglestatPanel(panel map[string]interface{}, defaults map[string]interface{}) {
migratetSinglestat(panel, defaults)
}
// migrateThresholds handles threshold migration for both singlestat panel types
// Both panel types now create DEFAULT_THRESHOLDS when threshold string is empty (consolidated behavior)
func migrateThresholds(angularOpts map[string]interface{}, defaults map[string]interface{}) {
if thresholds, ok := angularOpts["thresholds"].(string); ok {
if colors, ok := angularOpts["colors"].([]interface{}); ok {
if thresholds != "" {
// Non-empty thresholds: use normal migration
migrateThresholdsAndColors(defaults, thresholds, colors)
} else {
// Empty thresholds: use frontend DEFAULT_THRESHOLDS fallback (both panel types)
defaults["thresholds"] = map[string]interface{}{
"mode": "absolute",
"steps": []interface{}{
map[string]interface{}{
"color": "green",
"value": (*float64)(nil), // Use pointer to ensure field is present in JSON
},
map[string]interface{}{
"color": "red",
"value": 80,
},
},
}
}
}
}
}
// applyCommonAngularMigration applies migrations common to both singlestat types
func applyCommonAngularMigration(panel map[string]interface{}, defaults map[string]interface{}, options map[string]interface{}, angularOpts map[string]interface{}) {
// Migrate table column
// Based on sharedSingleStatPanelChangedHandler line ~125: options.reduceOptions.fields = `/^${prevPanel.tableColumn}$/`
if tableColumn, ok := angularOpts["tableColumn"].(string); ok && tableColumn != "" {
options["reduceOptions"].(map[string]interface{})["fields"] = "/^" + tableColumn + "$/"
}
// Migrate unit from format property (matches frontend sharedSingleStatPanelChangedHandler)
if format, ok := angularOpts["format"].(string); ok {
defaults["unit"] = format
}
// Migrate decimals
if decimals, ok := angularOpts["decimals"]; ok {
defaults["decimals"] = decimals
}
// Note: Frontend migrateFromAngularSinglestat does migrate nullPointMode to nullValueMode
// but the frontend's getSaveModel() method removes it, so we don't add it here
// if nullPointMode, ok := angularOpts["nullPointMode"]; ok {
// defaults["nullValueMode"] = nullPointMode
// }
// Migrate null text
if nullText, ok := angularOpts["nullText"].(string); ok {
defaults["noValue"] = nullText
}
// Migrate value mappings (thresholds should already be migrated)
valueMaps, _ := angularOpts["valueMaps"].([]interface{})
migrateValueMappings(angularOpts, defaults, valueMaps)
// Migrate sparkline configuration
// Based on statPanelChangedHandler lines ~20-23: sparkline migration logic
if sparkline, ok := angularOpts["sparkline"].(map[string]interface{}); ok {
if show, ok := sparkline["show"].(bool); ok && show {
options["graphMode"] = "area"
} else {
options["graphMode"] = "none"
}
} else {
// Default to no graph mode if no sparkline configuration
options["graphMode"] = "none"
}
// Migrate color configuration
// Based on statPanelChangedHandler lines ~25-38: colorBackground and colorValue migration
colorMode := determineColorMode(angularOpts)
options["colorMode"] = colorMode
// Sparkline color migration only happens when colorMode is "none"
if colorMode == "none" {
migrateSparklineColor(angularOpts, defaults, options)
}
// Migrate text mode
// Based on statPanelChangedHandler lines ~45-47: valueName === 'name' migration
if valueName, ok := angularOpts["valueName"].(string); ok && valueName == "name" {
options["textMode"] = "name"
}
if angularOpts["gauge"] != nil && angularOpts["gauge"].(map[string]interface{})["show"] == true {
defaults["min"] = angularOpts["gauge"].(map[string]interface{})["minValue"]
defaults["max"] = angularOpts["gauge"].(map[string]interface{})["maxValue"]
}
}
// applyCompleteStatPanelDefaults applies the complete stat panel defaults
// This matches the frontend's getPanelOptionsWithDefaults behavior after migration
func applyCompleteStatPanelDefaults(panel map[string]interface{}) {
// Get or create options object
options, exists := panel["options"].(map[string]interface{})
if !exists {
options = map[string]interface{}{}
panel["options"] = options
}
defaultOptions := getDefaultStatOptions()
// Merge defaults with existing options, but don't override existing values
// This matches the frontend's getPanelOptionsWithDefaults behavior
for key, defaultValue := range defaultOptions {
if _, exists := options[key]; !exists {
options[key] = defaultValue
}
}
}
// applySharedSinglestatMigration applies shared migration logic for all singlestat panels
// Based on sharedSingleStatMigrationHandler in packages/grafana-ui/src/components/SingleStatShared/SingleStatBaseOptions.ts
func applySharedSinglestatMigration(defaults map[string]interface{}) {
// Ensure thresholds have proper structure
if thresholds, ok := defaults["thresholds"].(map[string]interface{}); ok {
if steps, ok := thresholds["steps"].([]interface{}); ok {
// Ensure first threshold is -Infinity (represented as null in JSON)
if len(steps) > 0 {
if firstStep, ok := steps[0].(map[string]interface{}); ok {
if firstStep["value"] == nil {
firstStep["value"] = nil // Use null instead of -math.Inf(1)
}
}
}
}
}
// Handle percent/percentunit units
// Based on sharedSingleStatMigrationHandler lines ~280-300: percent/percentunit min/max handling
if unit, ok := defaults["unit"].(string); ok {
switch unit {
case "percent":
if defaults["min"] == nil {
defaults["min"] = 0
}
if defaults["max"] == nil {
defaults["max"] = 100
}
case "percentunit":
if defaults["min"] == nil {
defaults["min"] = 0
}
if defaults["max"] == nil {
defaults["max"] = 1
}
}
}
}
// Helper functions
func extractAngularOptions(panel map[string]interface{}) map[string]interface{} {
// Some panels might have angular options directly in the root
// Check for common angular properties
angularProps := []string{
"valueName", "tableColumn", "format", "decimals", "nullPointMode", "nullText",
"thresholds", "colors", "valueMaps", "gauge", "sparkline", "colorBackground", "colorValue",
}
for _, prop := range angularProps {
if _, exists := panel[prop]; exists {
return panel
}
}
return map[string]interface{}{}
}
// getReducerForValueName returns the mapped reducer or empty string for invalid values
func getReducerForValueName(valueName string) string {
reducerMap := map[string]string{
"min": "min",
"max": "max",
"mean": "mean",
"avg": "mean", // avg maps to mean
"median": "median",
"sum": "sum",
"count": "count",
"first": "firstNotNull",
"last": "lastNotNull",
"name": "lastNotNull",
"current": "lastNotNull",
"total": "sum",
}
if reducer, ok := reducerMap[valueName]; ok {
return reducer
}
return ""
}
func migrateThresholdsAndColors(defaults map[string]interface{}, thresholdsStr string, colors []interface{}) {
// Parse thresholds string (e.g., "10,20,30")
// Based on sharedSingleStatPanelChangedHandler lines ~145-165: Convert thresholds and color values
thresholds := []interface{}{}
thresholdValues := strings.Split(thresholdsStr, ",")
// Create threshold steps
for i, color := range colors {
step := map[string]interface{}{
"color": color,
}
if i == 0 {
// Frontend expects explicit null value for first step, not omitted field
// Use a pointer to ensure the field is present in JSON with null value
var nullValue *float64
step["value"] = nullValue
} else if i-1 < len(thresholdValues) {
if val, err := strconv.ParseFloat(strings.TrimSpace(thresholdValues[i-1]), 64); err == nil {
step["value"] = val
}
}
thresholds = append(thresholds, step)
}
defaults["thresholds"] = map[string]interface{}{
"mode": "absolute",
"steps": thresholds,
}
}
func migrateValueMappings(panel map[string]interface{}, defaults map[string]interface{}, valueMappings []interface{}) {
mappings := []interface{}{}
mappingType := panel["mappingType"]
// Check for inconsistent mapping configuration
// If panel has rangeMaps but mappingType is 1, or vice versa, fix it
hasValueMaps := panel["valueMaps"] != nil && IsArray(panel["valueMaps"]) && len(panel["valueMaps"].([]interface{})) > 0
hasRangeMaps := panel["rangeMaps"] != nil && IsArray(panel["rangeMaps"]) && len(panel["rangeMaps"].([]interface{})) > 0
if hasRangeMaps && mappingType == float64(1) {
mappingType = 2
} else if hasValueMaps && mappingType == float64(2) {
mappingType = 1
} else if mappingType == nil {
if hasValueMaps {
mappingType = 1
} else if hasRangeMaps {
mappingType = 2
}
}
switch mappingType {
case 1:
for _, valueMap := range valueMappings {
valueMapping := valueMap.(map[string]interface{})
upgradedMapping := upgradeOldAngularValueMapping(valueMapping, defaults["thresholds"])
if upgradedMapping != nil {
mappings = append(mappings, upgradedMapping)
}
}
case 2:
// Handle range mappings
if rangeMaps, ok := panel["rangeMaps"].([]interface{}); ok {
for _, rangeMap := range rangeMaps {
rangeMapping := rangeMap.(map[string]interface{})
upgradedMapping := upgradeOldAngularValueMapping(rangeMapping, defaults["thresholds"])
if upgradedMapping != nil {
mappings = append(mappings, upgradedMapping)
}
}
}
}
defaults["mappings"] = mappings
}
// upgradeOldAngularValueMapping converts old angular value mappings to new format
// Based on upgradeOldAngularValueMapping in packages/grafana-data/src/utils/valueMappings.ts
func upgradeOldAngularValueMapping(old map[string]interface{}, thresholds interface{}) map[string]interface{} {
valueMaps := map[string]interface{}{
"type": "value",
"options": map[string]interface{}{},
}
newMappings := []interface{}{}
// Use the color we would have picked from thresholds
// Frontend uses old.text to determine color, not old.value
var color interface{}
if text, ok := old["text"].(string); ok {
if numeric, err := parseNumericValue(text); err == nil {
if thresholdsMap, ok := thresholds.(map[string]interface{}); ok {
if steps, ok := thresholdsMap["steps"].([]interface{}); ok {
level := getActiveThreshold(numeric, steps)
if level != nil {
if levelColor, ok := level["color"]; ok {
color = levelColor
}
}
}
}
}
}
// Determine mapping type
mappingType := old["type"]
if mappingType == nil {
// Try to guess from available properties
if old["value"] != nil {
mappingType = 1 // ValueToText
} else if old["from"] != nil || old["to"] != nil {
mappingType = 2 // RangeToText
}
}
switch mappingType {
case 1: // ValueToText
if value, ok := old["value"]; ok && value != nil {
if valueStr, ok := value.(string); ok && valueStr == "null" {
newMappings = append(newMappings, map[string]interface{}{
"type": "special",
"options": map[string]interface{}{
"match": "null",
"result": map[string]interface{}{"text": old["text"], "color": color},
},
})
} else {
valueMaps["options"].(map[string]interface{})[fmt.Sprintf("%v", value)] = map[string]interface{}{
"text": old["text"],
"color": color,
}
}
}
case 2: // RangeToText
from := old["from"]
to := old["to"]
if (from != nil && fmt.Sprintf("%v", from) == "null") || (to != nil && fmt.Sprintf("%v", to) == "null") {
newMappings = append(newMappings, map[string]interface{}{
"type": "special",
"options": map[string]interface{}{
"match": "null",
"result": map[string]interface{}{"text": old["text"], "color": color},
},
})
} else {
var fromVal, toVal interface{}
if from != nil {
if fromStr, ok := from.(string); ok {
if fromFloat, err := strconv.ParseFloat(fromStr, 64); err == nil {
fromVal = fromFloat
}
} else {
fromVal = from
}
}
if to != nil {
if toStr, ok := to.(string); ok {
if toFloat, err := strconv.ParseFloat(toStr, 64); err == nil {
toVal = toFloat
}
} else {
toVal = to
}
}
newMappings = append(newMappings, map[string]interface{}{
"type": "range",
"options": map[string]interface{}{
"from": fromVal,
"to": toVal,
"result": map[string]interface{}{"text": old["text"], "color": color},
},
})
}
}
// Add valueMaps if it has options
if len(valueMaps["options"].(map[string]interface{})) > 0 {
newMappings = append([]interface{}{valueMaps}, newMappings...)
}
if len(newMappings) > 0 {
return newMappings[0].(map[string]interface{})
}
return nil
}
// getActiveThreshold finds the active threshold for a given value
// Based on getActiveThreshold in packages/grafana-data/src/field/thresholds.ts
func getActiveThreshold(value float64, steps []interface{}) map[string]interface{} {
for i := len(steps) - 1; i >= 0; i-- {
if step, ok := steps[i].(map[string]interface{}); ok {
if stepValue, ok := step["value"]; ok {
if stepValue == nil {
// First step with null value (represents -Infinity)
return step
}
if stepFloat, ok := stepValue.(float64); ok && value >= stepFloat {
return step
}
}
}
}
return nil
}
// parseNumericValue converts various types to float64 for threshold calculations
func parseNumericValue(value interface{}) (float64, error) {
switch v := value.(type) {
case string:
return strconv.ParseFloat(v, 64)
case float64:
return v, nil
case float32:
return float64(v), nil
case int:
return float64(v), nil
case int32:
return float64(v), nil
case int64:
return float64(v), nil
default:
return 0, fmt.Errorf("cannot convert %T to numeric value", value)
}
}
// createFieldConfigFromDefaults creates the proper fieldConfig structure from defaults
// and removes all legacy properties from the panel
func createFieldConfigFromDefaults(panel map[string]interface{}, defaults map[string]interface{}) {
// Ensure fieldConfig exists
if panel["fieldConfig"] == nil {
panel["fieldConfig"] = map[string]interface{}{
"defaults": map[string]interface{}{},
"overrides": []interface{}{},
}
}
fieldConfig := panel["fieldConfig"].(map[string]interface{})
fieldDefaults := fieldConfig["defaults"].(map[string]interface{})
// Copy all defaults to fieldConfig.defaults
for key, value := range defaults {
fieldDefaults[key] = value
}
// Note: Frontend doesn't add these extra fieldConfig defaults
// Color is handled in sparkline migration logic
// nullValueMode and unit are not added by frontend
// Remove all legacy properties from the panel
legacyProperties := []string{
"colors", "thresholds", "valueMaps", "grid", "legend", "mappingTypes", "gauge",
"autoMigrateFrom", "colorBackground", "colorValue", "format", "mappingType",
"nullPointMode", "postfix", "postfixFontSize", "prefix",
"prefixFontSize", "rangeMaps", "sparkline", "tableColumn", "valueFontSize",
"valueName", "aliasYAxis", "bars", "dashLength", "dashes", "fill", "fillGradient",
"lineInterpolation", "lineWidth", "pointRadius", "points", "spaceLength",
"stack", "steppedLine", "xAxis", "yAxes", "yAxis", "zIndex",
}
for _, prop := range legacyProperties {
delete(panel, prop)
}
}
// cleanupAngularProperties removes old angular properties after migration
// Based on PanelModel.clearPropertiesBeforePluginChange in public/app/features/dashboard/state/PanelModel.ts
// This function removes ALL properties except those in mustKeepProps to match frontend behavior exactly
func cleanupAngularProperties(panel map[string]interface{}) {
// Properties that must be kept (matching frontend mustKeepProps)
mustKeepProps := map[string]bool{
"id": true, "gridPos": true, "type": true, "title": true, "scopedVars": true,
"repeat": true, "repeatPanelId": true, "repeatDirection": true, "repeatedByRow": true,
"minSpan": true, "collapsed": true, "panels": true, "targets": true, "datasource": true,
"timeFrom": true, "timeShift": true, "hideTimeOverride": true, "description": true,
"links": true, "fullscreen": true, "isEditing": true, "isViewing": true,
"hasRefreshed": true, "events": true, "cacheTimeout": true, "queryCachingTTL": true,
"cachedPluginOptions": true, "transparent": true, "pluginVersion": true,
"fieldConfig": true, "options": true, // These are set by migration
"maxDataPoints": true, "interval": true, // Panel-level properties preserved by frontend
"autoMigrateFrom": true, // Preserve autoMigrateFrom for proper migration logic
}
// Remove ALL properties except those in mustKeepProps (matching frontend behavior)
for key := range panel {
if !mustKeepProps[key] {
delete(panel, key)
}
}
// Ensure all targets have refIds (matching frontend ensureQueryIds behavior)
ensureTargetRefIds(panel)
}
// ensureTargetRefIds assigns refIds to targets that don't have them
// This matches the frontend PanelModel.ensureQueryIds() behavior
func ensureTargetRefIds(panel map[string]interface{}) {
targets, ok := panel["targets"].([]interface{})
if !ok || len(targets) == 0 {
return
}
// Find existing refIds
existingRefIds := make(map[string]bool)
for _, targetInterface := range targets {
if target, ok := targetInterface.(map[string]interface{}); ok {
if refId, ok := target["refId"].(string); ok {
existingRefIds[refId] = true
}
}
}
// Assign refIds to targets that don't have them
letters := "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
letterIndex := 0
for _, targetInterface := range targets {
if target, ok := targetInterface.(map[string]interface{}); ok {
refId, hasRefId := target["refId"].(string)
if !hasRefId || refId == "" {
// Find next available refId
for letterIndex < len(letters) {
refId := string(letters[letterIndex])
if !existingRefIds[refId] {
target["refId"] = refId
existingRefIds[refId] = true
break
}
letterIndex++
}
letterIndex++
}
}
}
}
// removeDeprecatedVariableProperties removes deprecated properties from variables
// Based on DashboardMigrator.ts v28 migration: variable property cleanup
func removeDeprecatedVariableProperties(variable map[string]interface{}) {
@@ -843,45 +69,3 @@ func removeDeprecatedVariableProperties(variable map[string]interface{}) {
}
}
}
// determineColorMode determines the color mode based on angular options
func determineColorMode(angularOpts map[string]interface{}) string {
if colorBackground, ok := angularOpts["colorBackground"].(bool); ok && colorBackground {
return "background"
}
if colorValue, ok := angularOpts["colorValue"].(bool); ok && colorValue {
return "value"
}
return "none"
}
// migrateSparklineColor migrates sparkline color configuration when colorMode is "none"
// Based on statPanelChangedHandler lines 31-38
func migrateSparklineColor(angularOpts map[string]interface{}, defaults map[string]interface{}, options map[string]interface{}) {
sparkline, ok := angularOpts["sparkline"].(map[string]interface{})
if !ok {
return
}
show, ok := sparkline["show"].(bool)
if !ok || !show {
return
}
graphMode, ok := options["graphMode"].(string)
if !ok || graphMode != "area" {
return
}
lineColor, ok := sparkline["lineColor"].(string)
if !ok {
return
}
defaults["color"] = map[string]interface{}{
"mode": "fixed",
"fixedColor": lineColor,
}
}
@@ -5,101 +5,186 @@ import (
"testing"
)
func TestV28SinglestatMigration(t *testing.T) {
tests := []struct {
name string
input map[string]interface{}
expected map[string]interface{}
description string
}{
{
name: "migrate_range_maps_to_field_config_mappings",
input: map[string]interface{}{
"type": "singlestat",
"rangeMaps": []interface{}{
map[string]interface{}{
"from": "null",
"to": "N/A",
},
},
"mappingType": 1, // Inconsistent - should be 2 for rangeMaps
},
expected: map[string]interface{}{
"type": "stat",
"fieldConfig": map[string]interface{}{
"defaults": map[string]interface{}{
"mappings": []interface{}{
map[string]interface{}{
"options": map[string]interface{}{
"match": "null",
"result": map[string]interface{}{
"text": "N/A",
},
},
"type": "special",
},
},
},
},
},
description: "RangeMaps should migrate to fieldConfig.mappings, and inconsistent mappingType should be fixed to 2 (RangeToText)",
},
{
name: "migrate_sparkline_color_when_color_mode_none",
input: map[string]interface{}{
"type": "singlestat",
"colorMode": "None",
"sparkline": map[string]interface{}{
"lineColor": "rgb(31, 120, 193)",
},
},
expected: map[string]interface{}{
"type": "stat",
"fieldConfig": map[string]interface{}{
"defaults": map[string]interface{}{
"color": map[string]interface{}{
"mode": "fixed",
"fixedColor": "rgb(31, 120, 193)",
},
},
},
},
description: "Sparkline lineColor should migrate to fieldConfig.defaults.color only when colorMode is None",
},
}
type migrationTestCase struct {
name string
input map[string]interface{}
expected map[string]interface{}
}
func runMigrationTests(t *testing.T, tests []migrationTestCase, migrationFunc func(context.Context, map[string]interface{}) error) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
dashboard := map[string]interface{}{
"schemaVersion": 27,
"panels": []interface{}{tt.input},
// Create a copy of the input
dashboard := make(map[string]interface{})
for k, v := range tt.input {
dashboard[k] = v
}
err := V28(context.Background(), dashboard)
err := migrationFunc(context.Background(), dashboard)
if err != nil {
t.Fatalf("V28 migration failed: %v", err)
t.Fatalf("Migration failed: %v", err)
}
if dashboard["schemaVersion"] != 28 {
t.Errorf("Expected schemaVersion to be 28, got %v", dashboard["schemaVersion"])
// Verify the result matches expected
if !deepEqual(dashboard, tt.expected) {
t.Errorf("Migration result doesn't match expected.\nExpected: %+v\nGot: %+v", tt.expected, dashboard)
}
panels, ok := dashboard["panels"].([]interface{})
if !ok || len(panels) == 0 {
t.Fatalf("Expected panels array with at least one panel")
}
panel, ok := panels[0].(map[string]interface{})
if !ok {
t.Fatalf("Expected panel to be a map")
}
// Verify panel type was changed to stat
if panel["type"] != "stat" {
t.Errorf("Expected panel type to be 'stat', got %v", panel["type"])
}
t.Logf("✓ %s: %s", tt.name, tt.description)
})
}
}
func deepEqual(a, b interface{}) bool {
// Simple deep comparison for test purposes
// This is a simplified version - in production you'd use reflect.DeepEqual or similar
switch aVal := a.(type) {
case map[string]interface{}:
bVal, ok := b.(map[string]interface{})
if !ok || len(aVal) != len(bVal) {
return false
}
for k, v := range aVal {
if !deepEqual(v, bVal[k]) {
return false
}
}
return true
case []interface{}:
bVal, ok := b.([]interface{})
if !ok || len(aVal) != len(bVal) {
return false
}
for i, v := range aVal {
if !deepEqual(v, bVal[i]) {
return false
}
}
return true
default:
return a == b
}
}
func TestV28(t *testing.T) {
tests := []migrationTestCase{
{
name: "v28 removes deprecated variable properties",
input: map[string]interface{}{
"title": "V28 Variable Properties Migration Test Dashboard",
"schemaVersion": 27,
"templating": map[string]interface{}{
"list": []interface{}{
map[string]interface{}{
"name": "var1",
"tags": []interface{}{"tag1", "tag2"},
"tagsQuery": "query_string",
"tagValuesQuery": "values_query",
"useTags": true,
"type": "query",
},
map[string]interface{}{
"name": "var2",
"tags": []interface{}{},
"tagsQuery": "", // Empty string should not be removed
"tagValuesQuery": "", // Empty string should not be removed
"useTags": false, // False should not be removed
"type": "custom",
},
},
},
"panels": []interface{}{
map[string]interface{}{
"type": "singlestat",
"title": "Singlestat Panel (unchanged by v28)",
"id": 1,
},
},
},
expected: map[string]interface{}{
"title": "V28 Variable Properties Migration Test Dashboard",
"schemaVersion": 28,
"templating": map[string]interface{}{
"list": []interface{}{
map[string]interface{}{
"name": "var1",
"type": "query",
// tags, tagsQuery, tagValuesQuery, useTags should be removed
},
map[string]interface{}{
"name": "var2",
"tagsQuery": "", // Empty string preserved
"tagValuesQuery": "", // Empty string preserved
"useTags": false, // False preserved
"type": "custom",
// only tags should be removed
},
},
},
"panels": []interface{}{
map[string]interface{}{
"type": "singlestat",
"title": "Singlestat Panel (unchanged by v28)",
"id": 1,
},
},
},
},
{
name: "v28 handles dashboard without templating",
input: map[string]interface{}{
"title": "Dashboard without templating",
"schemaVersion": 27,
"panels": []interface{}{
map[string]interface{}{
"type": "singlestat",
"title": "Singlestat Panel",
"id": 1,
},
},
},
expected: map[string]interface{}{
"title": "Dashboard without templating",
"schemaVersion": 28,
"panels": []interface{}{
map[string]interface{}{
"type": "singlestat",
"title": "Singlestat Panel",
"id": 1,
},
},
},
},
{
name: "v28 handles empty templating list",
input: map[string]interface{}{
"title": "Dashboard with empty templating",
"schemaVersion": 27,
"templating": map[string]interface{}{
"list": []interface{}{},
},
"panels": []interface{}{
map[string]interface{}{
"type": "singlestat",
"title": "Singlestat Panel",
"id": 1,
},
},
},
expected: map[string]interface{}{
"title": "Dashboard with empty templating",
"schemaVersion": 28,
"templating": map[string]interface{}{
"list": []interface{}{},
},
"panels": []interface{}{
map[string]interface{}{
"type": "singlestat",
"title": "Singlestat Panel",
"id": 1,
},
},
},
},
}
runMigrationTests(t, tests, V28)
}
@@ -327,60 +327,12 @@
}
},
{
"autoMigrateFrom": "table-old",
"columns": [],
"datasource": {
"uid": "gdev-mssql"
},
"fieldConfig": {
"defaults": {
"custom": {
"cellOptions": {
"type": "auto"
},
"footer": {
"reducers": []
},
"inspect": false
},
"decimals": 2,
"displayName": "",
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
},
"unit": "short"
},
"overrides": [
{
"matcher": {
"id": "byName",
"options": "Time"
},
"properties": [
{
"id": "displayName",
"value": "Time"
},
{
"id": "unit",
"value": "time: YYYY-MM-DD HH:mm:ss"
},
{
"id": "custom.align"
}
]
}
]
},
"fontSize": "100%",
"gridPos": {
"h": 10,
"w": 24,
@@ -388,11 +340,35 @@
"y": 18
},
"id": 4,
"options": {
"cellHeight": "sm",
"showHeader": true
"scroll": true,
"showHeader": true,
"sort": {
"col": 0,
"desc": true
},
"pluginVersion": "12.1.0",
"styles": [
{
"alias": "Time",
"align": "auto",
"dateFormat": "YYYY-MM-DD HH:mm:ss",
"pattern": "Time",
"type": "date"
},
{
"alias": "",
"align": "auto",
"colors": [
"rgba(245, 54, 54, 0.9)",
"rgba(237, 129, 40, 0.89)",
"rgba(50, 172, 45, 0.97)"
],
"decimals": 2,
"pattern": "/.*/",
"thresholds": [],
"type": "number",
"unit": "short"
}
],
"targets": [
{
"alias": "",
@@ -405,14 +381,7 @@
}
],
"title": "Values",
"transformations": [
{
"id": "merge",
"options": {
"reducers": []
}
}
],
"transform": "table",
"type": "table"
}
],
@@ -77,40 +77,12 @@
"links": [],
"panels": [
{
"autoMigrateFrom": "table-old",
"columns": [],
"datasource": {
"uid": "gdev-mssql-ds-tests"
},
"fieldConfig": {
"defaults": {
"custom": {
"cellOptions": {
"type": "auto"
},
"footer": {
"reducers": []
},
"inspect": false
},
"decimals": 2,
"displayName": "",
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
},
"unit": "short"
},
"overrides": []
},
"fontSize": "100%",
"gridPos": {
"h": 4,
"w": 24,
@@ -118,11 +90,28 @@
"y": 0
},
"id": 2,
"options": {
"cellHeight": "sm",
"showHeader": true
"scroll": true,
"showHeader": true,
"sort": {
"col": 0,
"desc": true
},
"pluginVersion": "12.1.0",
"styles": [
{
"alias": "",
"align": "auto",
"colors": [
"rgba(245, 54, 54, 0.9)",
"rgba(237, 129, 40, 0.89)",
"rgba(50, 172, 45, 0.97)"
],
"decimals": 2,
"pattern": "/.*/",
"thresholds": [],
"type": "string",
"unit": "short"
}
],
"targets": [
{
"alias": "",
@@ -135,71 +124,16 @@
}
],
"title": "Data types",
"transformations": [
{
"id": "merge",
"options": {
"reducers": []
}
}
],
"transform": "table",
"type": "table"
},
{
"autoMigrateFrom": "table-old",
"columns": [],
"datasource": {
"uid": "gdev-mssql-ds-tests"
},
"fieldConfig": {
"defaults": {
"custom": {
"cellOptions": {
"type": "auto"
},
"footer": {
"reducers": []
},
"inspect": false
},
"decimals": 2,
"displayName": "",
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
},
"unit": "short"
},
"overrides": [
{
"matcher": {
"id": "byName",
"options": "time"
},
"properties": [
{
"id": "displayName",
"value": "Time"
},
{
"id": "unit",
"value": "time: YYYY-MM-DD HH:mm:ss"
},
{
"id": "custom.align"
}
]
}
]
},
"fontSize": "100%",
"gridPos": {
"h": 3,
"w": 6,
@@ -207,11 +141,35 @@
"y": 4
},
"id": 32,
"options": {
"cellHeight": "sm",
"showHeader": true
"scroll": true,
"showHeader": true,
"sort": {
"col": 0,
"desc": true
},
"pluginVersion": "12.1.0",
"styles": [
{
"alias": "Time",
"align": "auto",
"dateFormat": "YYYY-MM-DD HH:mm:ss",
"pattern": "time",
"type": "date"
},
{
"alias": "",
"align": "auto",
"colors": [
"rgba(245, 54, 54, 0.9)",
"rgba(237, 129, 40, 0.89)",
"rgba(50, 172, 45, 0.97)"
],
"decimals": 2,
"pattern": "/.*/",
"thresholds": [],
"type": "number",
"unit": "short"
}
],
"targets": [
{
"alias": "",
@@ -225,71 +183,16 @@
}
],
"title": "cast(null as bigint) as time",
"transformations": [
{
"id": "merge",
"options": {
"reducers": []
}
}
],
"transform": "table",
"type": "table"
},
{
"autoMigrateFrom": "table-old",
"columns": [],
"datasource": {
"uid": "gdev-mssql-ds-tests"
},
"fieldConfig": {
"defaults": {
"custom": {
"cellOptions": {
"type": "auto"
},
"footer": {
"reducers": []
},
"inspect": false
},
"decimals": 2,
"displayName": "",
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
},
"unit": "short"
},
"overrides": [
{
"matcher": {
"id": "byName",
"options": "time"
},
"properties": [
{
"id": "displayName",
"value": "Time"
},
{
"id": "unit",
"value": "time: YYYY-MM-DD HH:mm:ss"
},
{
"id": "custom.align"
}
]
}
]
},
"fontSize": "100%",
"gridPos": {
"h": 3,
"w": 6,
@@ -297,11 +200,35 @@
"y": 4
},
"id": 33,
"options": {
"cellHeight": "sm",
"showHeader": true
"scroll": true,
"showHeader": true,
"sort": {
"col": 0,
"desc": true
},
"pluginVersion": "12.1.0",
"styles": [
{
"alias": "Time",
"align": "auto",
"dateFormat": "YYYY-MM-DD HH:mm:ss",
"pattern": "time",
"type": "date"
},
{
"alias": "",
"align": "auto",
"colors": [
"rgba(245, 54, 54, 0.9)",
"rgba(237, 129, 40, 0.89)",
"rgba(50, 172, 45, 0.97)"
],
"decimals": 2,
"pattern": "/.*/",
"thresholds": [],
"type": "number",
"unit": "short"
}
],
"targets": [
{
"alias": "",
@@ -315,71 +242,16 @@
}
],
"title": "cast(null as datetime) as time",
"transformations": [
{
"id": "merge",
"options": {
"reducers": []
}
}
],
"transform": "table",
"type": "table"
},
{
"autoMigrateFrom": "table-old",
"columns": [],
"datasource": {
"uid": "gdev-mssql-ds-tests"
},
"fieldConfig": {
"defaults": {
"custom": {
"cellOptions": {
"type": "auto"
},
"footer": {
"reducers": []
},
"inspect": false
},
"decimals": 2,
"displayName": "",
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
},
"unit": "short"
},
"overrides": [
{
"matcher": {
"id": "byName",
"options": "time"
},
"properties": [
{
"id": "displayName",
"value": "Time"
},
{
"id": "unit",
"value": "time: YYYY-MM-DD HH:mm:ss"
},
{
"id": "custom.align"
}
]
}
]
},
"fontSize": "100%",
"gridPos": {
"h": 3,
"w": 6,
@@ -387,11 +259,35 @@
"y": 4
},
"id": 34,
"options": {
"cellHeight": "sm",
"showHeader": true
"scroll": true,
"showHeader": true,
"sort": {
"col": 0,
"desc": true
},
"pluginVersion": "12.1.0",
"styles": [
{
"alias": "Time",
"align": "auto",
"dateFormat": "YYYY-MM-DD HH:mm:ss",
"pattern": "time",
"type": "date"
},
{
"alias": "",
"align": "auto",
"colors": [
"rgba(245, 54, 54, 0.9)",
"rgba(237, 129, 40, 0.89)",
"rgba(50, 172, 45, 0.97)"
],
"decimals": 2,
"pattern": "/.*/",
"thresholds": [],
"type": "number",
"unit": "short"
}
],
"targets": [
{
"alias": "",
@@ -405,71 +301,16 @@
}
],
"title": "GETDATE() as time",
"transformations": [
{
"id": "merge",
"options": {
"reducers": []
}
}
],
"transform": "table",
"type": "table"
},
{
"autoMigrateFrom": "table-old",
"columns": [],
"datasource": {
"uid": "gdev-mssql-ds-tests"
},
"fieldConfig": {
"defaults": {
"custom": {
"cellOptions": {
"type": "auto"
},
"footer": {
"reducers": []
},
"inspect": false
},
"decimals": 2,
"displayName": "",
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
},
"unit": "short"
},
"overrides": [
{
"matcher": {
"id": "byName",
"options": "time"
},
"properties": [
{
"id": "displayName",
"value": "Time"
},
{
"id": "unit",
"value": "time: YYYY-MM-DD HH:mm:ss"
},
{
"id": "custom.align"
}
]
}
]
},
"fontSize": "100%",
"gridPos": {
"h": 3,
"w": 6,
@@ -477,11 +318,35 @@
"y": 4
},
"id": 35,
"options": {
"cellHeight": "sm",
"showHeader": true
"scroll": true,
"showHeader": true,
"sort": {
"col": 0,
"desc": true
},
"pluginVersion": "12.1.0",
"styles": [
{
"alias": "Time",
"align": "auto",
"dateFormat": "YYYY-MM-DD HH:mm:ss",
"pattern": "time",
"type": "date"
},
{
"alias": "",
"align": "auto",
"colors": [
"rgba(245, 54, 54, 0.9)",
"rgba(237, 129, 40, 0.89)",
"rgba(50, 172, 45, 0.97)"
],
"decimals": 2,
"pattern": "/.*/",
"thresholds": [],
"type": "number",
"unit": "short"
}
],
"targets": [
{
"alias": "",
@@ -495,14 +360,7 @@
}
],
"title": "GETUTCDATE() as time",
"transformations": [
{
"id": "merge",
"options": {
"reducers": []
}
}
],
"transform": "table",
"type": "table"
},
{
@@ -329,60 +329,12 @@
}
},
{
"autoMigrateFrom": "table-old",
"columns": [],
"datasource": {
"uid": "gdev-mysql"
},
"fieldConfig": {
"defaults": {
"custom": {
"cellOptions": {
"type": "auto"
},
"footer": {
"reducers": []
},
"inspect": false
},
"decimals": 2,
"displayName": "",
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
},
"unit": "short"
},
"overrides": [
{
"matcher": {
"id": "byName",
"options": "Time"
},
"properties": [
{
"id": "displayName",
"value": "Time"
},
{
"id": "unit",
"value": "time: YYYY-MM-DD HH:mm:ss"
},
{
"id": "custom.align"
}
]
}
]
},
"fontSize": "100%",
"gridPos": {
"h": 9,
"w": 24,
@@ -390,11 +342,36 @@
"y": 18
},
"id": 6,
"options": {
"cellHeight": "sm",
"showHeader": true
"scroll": true,
"showHeader": true,
"sort": {
"col": 0,
"desc": true
},
"pluginVersion": "12.1.0",
"styles": [
{
"alias": "Time",
"align": "auto",
"dateFormat": "YYYY-MM-DD HH:mm:ss",
"link": false,
"pattern": "Time",
"type": "date"
},
{
"alias": "",
"align": "auto",
"colors": [
"rgba(245, 54, 54, 0.9)",
"rgba(237, 129, 40, 0.89)",
"rgba(50, 172, 45, 0.97)"
],
"decimals": 2,
"pattern": "/.*/",
"thresholds": [],
"type": "number",
"unit": "short"
}
],
"targets": [
{
"alias": "",
@@ -408,14 +385,7 @@
}
],
"title": "Values",
"transformations": [
{
"id": "merge",
"options": {
"reducers": []
}
}
],
"transform": "table",
"type": "table"
}
],
@@ -77,40 +77,12 @@
"links": [],
"panels": [
{
"autoMigrateFrom": "table-old",
"columns": [],
"datasource": {
"uid": "gdev-mysql-ds-tests"
},
"fieldConfig": {
"defaults": {
"custom": {
"cellOptions": {
"type": "auto"
},
"footer": {
"reducers": []
},
"inspect": false
},
"decimals": 2,
"displayName": "",
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
},
"unit": "short"
},
"overrides": []
},
"fontSize": "100%",
"gridPos": {
"h": 4,
"w": 24,
@@ -118,11 +90,28 @@
"y": 0
},
"id": 2,
"options": {
"cellHeight": "sm",
"showHeader": true
"scroll": true,
"showHeader": true,
"sort": {
"col": 0,
"desc": true
},
"pluginVersion": "12.1.0",
"styles": [
{
"alias": "",
"align": "auto",
"colors": [
"rgba(245, 54, 54, 0.9)",
"rgba(237, 129, 40, 0.89)",
"rgba(50, 172, 45, 0.97)"
],
"decimals": 2,
"pattern": "/.*/",
"thresholds": [],
"type": "string",
"unit": "short"
}
],
"targets": [
{
"alias": "",
@@ -135,71 +124,16 @@
}
],
"title": "Data types",
"transformations": [
{
"id": "merge",
"options": {
"reducers": []
}
}
],
"transform": "table",
"type": "table"
},
{
"autoMigrateFrom": "table-old",
"columns": [],
"datasource": {
"uid": "gdev-mysql-ds-tests"
},
"fieldConfig": {
"defaults": {
"custom": {
"cellOptions": {
"type": "auto"
},
"footer": {
"reducers": []
},
"inspect": false
},
"decimals": 2,
"displayName": "",
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
},
"unit": "short"
},
"overrides": [
{
"matcher": {
"id": "byName",
"options": "time_sec"
},
"properties": [
{
"id": "displayName",
"value": "Time"
},
{
"id": "unit",
"value": "time: YYYY-MM-DD HH:mm:ss"
},
{
"id": "custom.align"
}
]
}
]
},
"fontSize": "100%",
"gridPos": {
"h": 3,
"w": 6,
@@ -207,11 +141,35 @@
"y": 4
},
"id": 32,
"options": {
"cellHeight": "sm",
"showHeader": true
"scroll": true,
"showHeader": true,
"sort": {
"col": 0,
"desc": true
},
"pluginVersion": "12.1.0",
"styles": [
{
"alias": "Time",
"align": "auto",
"dateFormat": "YYYY-MM-DD HH:mm:ss",
"pattern": "time_sec",
"type": "date"
},
{
"alias": "",
"align": "auto",
"colors": [
"rgba(245, 54, 54, 0.9)",
"rgba(237, 129, 40, 0.89)",
"rgba(50, 172, 45, 0.97)"
],
"decimals": 2,
"pattern": "/.*/",
"thresholds": [],
"type": "number",
"unit": "short"
}
],
"targets": [
{
"alias": "",
@@ -225,71 +183,16 @@
}
],
"title": "cast(null as unsigned integer) as time",
"transformations": [
{
"id": "merge",
"options": {
"reducers": []
}
}
],
"transform": "table",
"type": "table"
},
{
"autoMigrateFrom": "table-old",
"columns": [],
"datasource": {
"uid": "gdev-mysql-ds-tests"
},
"fieldConfig": {
"defaults": {
"custom": {
"cellOptions": {
"type": "auto"
},
"footer": {
"reducers": []
},
"inspect": false
},
"decimals": 2,
"displayName": "",
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
},
"unit": "short"
},
"overrides": [
{
"matcher": {
"id": "byName",
"options": "time_sec"
},
"properties": [
{
"id": "displayName",
"value": "Time"
},
{
"id": "unit",
"value": "time: YYYY-MM-DD HH:mm:ss"
},
{
"id": "custom.align"
}
]
}
]
},
"fontSize": "100%",
"gridPos": {
"h": 3,
"w": 6,
@@ -297,11 +200,35 @@
"y": 4
},
"id": 33,
"options": {
"cellHeight": "sm",
"showHeader": true
"scroll": true,
"showHeader": true,
"sort": {
"col": 0,
"desc": true
},
"pluginVersion": "12.1.0",
"styles": [
{
"alias": "Time",
"align": "auto",
"dateFormat": "YYYY-MM-DD HH:mm:ss",
"pattern": "time_sec",
"type": "date"
},
{
"alias": "",
"align": "auto",
"colors": [
"rgba(245, 54, 54, 0.9)",
"rgba(237, 129, 40, 0.89)",
"rgba(50, 172, 45, 0.97)"
],
"decimals": 2,
"pattern": "/.*/",
"thresholds": [],
"type": "number",
"unit": "short"
}
],
"targets": [
{
"alias": "",
@@ -315,71 +242,16 @@
}
],
"title": "cast(null as datetime) as time",
"transformations": [
{
"id": "merge",
"options": {
"reducers": []
}
}
],
"transform": "table",
"type": "table"
},
{
"autoMigrateFrom": "table-old",
"columns": [],
"datasource": {
"uid": "gdev-mysql-ds-tests"
},
"fieldConfig": {
"defaults": {
"custom": {
"cellOptions": {
"type": "auto"
},
"footer": {
"reducers": []
},
"inspect": false
},
"decimals": 2,
"displayName": "",
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
},
"unit": "short"
},
"overrides": [
{
"matcher": {
"id": "byName",
"options": "time_sec"
},
"properties": [
{
"id": "displayName",
"value": "Time"
},
{
"id": "unit",
"value": "time: YYYY-MM-DD HH:mm:ss"
},
{
"id": "custom.align"
}
]
}
]
},
"fontSize": "100%",
"gridPos": {
"h": 3,
"w": 6,
@@ -387,11 +259,35 @@
"y": 4
},
"id": 34,
"options": {
"cellHeight": "sm",
"showHeader": true
"scroll": true,
"showHeader": true,
"sort": {
"col": 0,
"desc": true
},
"pluginVersion": "12.1.0",
"styles": [
{
"alias": "Time",
"align": "auto",
"dateFormat": "YYYY-MM-DD HH:mm:ss",
"pattern": "time_sec",
"type": "date"
},
{
"alias": "",
"align": "auto",
"colors": [
"rgba(245, 54, 54, 0.9)",
"rgba(237, 129, 40, 0.89)",
"rgba(50, 172, 45, 0.97)"
],
"decimals": 2,
"pattern": "/.*/",
"thresholds": [],
"type": "number",
"unit": "short"
}
],
"targets": [
{
"alias": "",
@@ -405,71 +301,16 @@
}
],
"title": "cast()NOW() as datetime) as time",
"transformations": [
{
"id": "merge",
"options": {
"reducers": []
}
}
],
"transform": "table",
"type": "table"
},
{
"autoMigrateFrom": "table-old",
"columns": [],
"datasource": {
"uid": "gdev-mysql-ds-tests"
},
"fieldConfig": {
"defaults": {
"custom": {
"cellOptions": {
"type": "auto"
},
"footer": {
"reducers": []
},
"inspect": false
},
"decimals": 2,
"displayName": "",
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
},
"unit": "short"
},
"overrides": [
{
"matcher": {
"id": "byName",
"options": "time"
},
"properties": [
{
"id": "displayName",
"value": "Time"
},
{
"id": "unit",
"value": "time: YYYY-MM-DD HH:mm:ss"
},
{
"id": "custom.align"
}
]
}
]
},
"fontSize": "100%",
"gridPos": {
"h": 3,
"w": 6,
@@ -477,11 +318,35 @@
"y": 4
},
"id": 35,
"options": {
"cellHeight": "sm",
"showHeader": true
"scroll": true,
"showHeader": true,
"sort": {
"col": 0,
"desc": true
},
"pluginVersion": "12.1.0",
"styles": [
{
"alias": "Time",
"align": "auto",
"dateFormat": "YYYY-MM-DD HH:mm:ss",
"pattern": "time",
"type": "date"
},
{
"alias": "",
"align": "auto",
"colors": [
"rgba(245, 54, 54, 0.9)",
"rgba(237, 129, 40, 0.89)",
"rgba(50, 172, 45, 0.97)"
],
"decimals": 2,
"pattern": "/.*/",
"thresholds": [],
"type": "number",
"unit": "short"
}
],
"targets": [
{
"alias": "",
@@ -495,14 +360,7 @@
}
],
"title": "NOW() as time",
"transformations": [
{
"id": "merge",
"options": {
"reducers": []
}
}
],
"transform": "table",
"type": "table"
},
{
@@ -371,60 +371,12 @@
}
},
{
"autoMigrateFrom": "table-old",
"columns": [],
"datasource": {
"uid": "gdev-postgres"
},
"fieldConfig": {
"defaults": {
"custom": {
"cellOptions": {
"type": "auto"
},
"footer": {
"reducers": []
},
"inspect": false
},
"decimals": 2,
"displayName": "",
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
},
"unit": "short"
},
"overrides": [
{
"matcher": {
"id": "byName",
"options": "Time"
},
"properties": [
{
"id": "displayName",
"value": "Time"
},
{
"id": "unit",
"value": "time: YYYY-MM-DD HH:mm:ss"
},
{
"id": "custom.align"
}
]
}
]
},
"fontSize": "100%",
"gridPos": {
"h": 9,
"w": 24,
@@ -432,11 +384,36 @@
"y": 18
},
"id": 6,
"options": {
"cellHeight": "sm",
"showHeader": true
"scroll": true,
"showHeader": true,
"sort": {
"col": 0,
"desc": true
},
"pluginVersion": "12.1.0",
"styles": [
{
"alias": "Time",
"align": "auto",
"dateFormat": "YYYY-MM-DD HH:mm:ss",
"link": false,
"pattern": "Time",
"type": "date"
},
{
"alias": "",
"align": "auto",
"colors": [
"rgba(245, 54, 54, 0.9)",
"rgba(237, 129, 40, 0.89)",
"rgba(50, 172, 45, 0.97)"
],
"decimals": 2,
"pattern": "/.*/",
"thresholds": [],
"type": "number",
"unit": "short"
}
],
"targets": [
{
"alias": "",
@@ -450,14 +427,7 @@
}
],
"title": "Values",
"transformations": [
{
"id": "merge",
"options": {
"reducers": []
}
}
],
"transform": "table",
"type": "table"
}
],
@@ -77,40 +77,12 @@
"links": [],
"panels": [
{
"autoMigrateFrom": "table-old",
"columns": [],
"datasource": {
"uid": "gdev-postgres-ds-tests"
},
"fieldConfig": {
"defaults": {
"custom": {
"cellOptions": {
"type": "auto"
},
"footer": {
"reducers": []
},
"inspect": false
},
"decimals": 2,
"displayName": "",
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
},
"unit": "short"
},
"overrides": []
},
"fontSize": "100%",
"gridPos": {
"h": 4,
"w": 24,
@@ -118,11 +90,28 @@
"y": 0
},
"id": 2,
"options": {
"cellHeight": "sm",
"showHeader": true
"scroll": true,
"showHeader": true,
"sort": {
"col": 1,
"desc": false
},
"pluginVersion": "12.1.0",
"styles": [
{
"alias": "",
"align": "auto",
"colors": [
"rgba(245, 54, 54, 0.9)",
"rgba(237, 129, 40, 0.89)",
"rgba(50, 172, 45, 0.97)"
],
"decimals": 2,
"pattern": "/.*/",
"thresholds": [],
"type": "string",
"unit": "short"
}
],
"targets": [
{
"alias": "",
@@ -135,71 +124,16 @@
}
],
"title": "Data types",
"transformations": [
{
"id": "merge",
"options": {
"reducers": []
}
}
],
"transform": "table",
"type": "table"
},
{
"autoMigrateFrom": "table-old",
"columns": [],
"datasource": {
"uid": "gdev-postgres-ds-tests"
},
"fieldConfig": {
"defaults": {
"custom": {
"cellOptions": {
"type": "auto"
},
"footer": {
"reducers": []
},
"inspect": false
},
"decimals": 2,
"displayName": "",
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
},
"unit": "short"
},
"overrides": [
{
"matcher": {
"id": "byName",
"options": "time"
},
"properties": [
{
"id": "displayName",
"value": "Time"
},
{
"id": "unit",
"value": "time: YYYY-MM-DD HH:mm:ss"
},
{
"id": "custom.align"
}
]
}
]
},
"fontSize": "100%",
"gridPos": {
"h": 3,
"w": 6,
@@ -207,11 +141,35 @@
"y": 4
},
"id": 32,
"options": {
"cellHeight": "sm",
"showHeader": true
"scroll": true,
"showHeader": true,
"sort": {
"col": 0,
"desc": true
},
"pluginVersion": "12.1.0",
"styles": [
{
"alias": "Time",
"align": "auto",
"dateFormat": "YYYY-MM-DD HH:mm:ss",
"pattern": "time",
"type": "date"
},
{
"alias": "",
"align": "auto",
"colors": [
"rgba(245, 54, 54, 0.9)",
"rgba(237, 129, 40, 0.89)",
"rgba(50, 172, 45, 0.97)"
],
"decimals": 2,
"pattern": "/.*/",
"thresholds": [],
"type": "number",
"unit": "short"
}
],
"targets": [
{
"alias": "",
@@ -225,71 +183,16 @@
}
],
"title": "cast(null as bigint) as time",
"transformations": [
{
"id": "merge",
"options": {
"reducers": []
}
}
],
"transform": "table",
"type": "table"
},
{
"autoMigrateFrom": "table-old",
"columns": [],
"datasource": {
"uid": "gdev-postgres-ds-tests"
},
"fieldConfig": {
"defaults": {
"custom": {
"cellOptions": {
"type": "auto"
},
"footer": {
"reducers": []
},
"inspect": false
},
"decimals": 2,
"displayName": "",
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
},
"unit": "short"
},
"overrides": [
{
"matcher": {
"id": "byName",
"options": "time"
},
"properties": [
{
"id": "displayName",
"value": "Time"
},
{
"id": "unit",
"value": "time: YYYY-MM-DD HH:mm:ss"
},
{
"id": "custom.align"
}
]
}
]
},
"fontSize": "100%",
"gridPos": {
"h": 3,
"w": 6,
@@ -297,11 +200,35 @@
"y": 4
},
"id": 33,
"options": {
"cellHeight": "sm",
"showHeader": true
"scroll": true,
"showHeader": true,
"sort": {
"col": 0,
"desc": true
},
"pluginVersion": "12.1.0",
"styles": [
{
"alias": "Time",
"align": "auto",
"dateFormat": "YYYY-MM-DD HH:mm:ss",
"pattern": "time",
"type": "date"
},
{
"alias": "",
"align": "auto",
"colors": [
"rgba(245, 54, 54, 0.9)",
"rgba(237, 129, 40, 0.89)",
"rgba(50, 172, 45, 0.97)"
],
"decimals": 2,
"pattern": "/.*/",
"thresholds": [],
"type": "number",
"unit": "short"
}
],
"targets": [
{
"alias": "",
@@ -315,71 +242,16 @@
}
],
"title": "cast(null as datetime) as time",
"transformations": [
{
"id": "merge",
"options": {
"reducers": []
}
}
],
"transform": "table",
"type": "table"
},
{
"autoMigrateFrom": "table-old",
"columns": [],
"datasource": {
"uid": "gdev-postgres-ds-tests"
},
"fieldConfig": {
"defaults": {
"custom": {
"cellOptions": {
"type": "auto"
},
"footer": {
"reducers": []
},
"inspect": false
},
"decimals": 2,
"displayName": "",
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
},
"unit": "short"
},
"overrides": [
{
"matcher": {
"id": "byName",
"options": "time"
},
"properties": [
{
"id": "displayName",
"value": "Time"
},
{
"id": "unit",
"value": "time: YYYY-MM-DD HH:mm:ss"
},
{
"id": "custom.align"
}
]
}
]
},
"fontSize": "100%",
"gridPos": {
"h": 3,
"w": 6,
@@ -387,11 +259,35 @@
"y": 4
},
"id": 34,
"options": {
"cellHeight": "sm",
"showHeader": true
"scroll": true,
"showHeader": true,
"sort": {
"col": 0,
"desc": true
},
"pluginVersion": "12.1.0",
"styles": [
{
"alias": "Time",
"align": "auto",
"dateFormat": "YYYY-MM-DD HH:mm:ss",
"pattern": "time",
"type": "date"
},
{
"alias": "",
"align": "auto",
"colors": [
"rgba(245, 54, 54, 0.9)",
"rgba(237, 129, 40, 0.89)",
"rgba(50, 172, 45, 0.97)"
],
"decimals": 2,
"pattern": "/.*/",
"thresholds": [],
"type": "number",
"unit": "short"
}
],
"targets": [
{
"alias": "",
@@ -405,71 +301,16 @@
}
],
"title": "localtimestamp as time",
"transformations": [
{
"id": "merge",
"options": {
"reducers": []
}
}
],
"transform": "table",
"type": "table"
},
{
"autoMigrateFrom": "table-old",
"columns": [],
"datasource": {
"uid": "gdev-postgres-ds-tests"
},
"fieldConfig": {
"defaults": {
"custom": {
"cellOptions": {
"type": "auto"
},
"footer": {
"reducers": []
},
"inspect": false
},
"decimals": 2,
"displayName": "",
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
},
"unit": "short"
},
"overrides": [
{
"matcher": {
"id": "byName",
"options": "time"
},
"properties": [
{
"id": "displayName",
"value": "Time"
},
{
"id": "unit",
"value": "time: YYYY-MM-DD HH:mm:ss"
},
{
"id": "custom.align"
}
]
}
]
},
"fontSize": "100%",
"gridPos": {
"h": 3,
"w": 6,
@@ -477,11 +318,35 @@
"y": 4
},
"id": 35,
"options": {
"cellHeight": "sm",
"showHeader": true
"scroll": true,
"showHeader": true,
"sort": {
"col": 0,
"desc": true
},
"pluginVersion": "12.1.0",
"styles": [
{
"alias": "Time",
"align": "auto",
"dateFormat": "YYYY-MM-DD HH:mm:ss",
"pattern": "time",
"type": "date"
},
{
"alias": "",
"align": "auto",
"colors": [
"rgba(245, 54, 54, 0.9)",
"rgba(237, 129, 40, 0.89)",
"rgba(50, 172, 45, 0.97)"
],
"decimals": 2,
"pattern": "/.*/",
"thresholds": [],
"type": "number",
"unit": "short"
}
],
"targets": [
{
"alias": "",
@@ -495,14 +360,7 @@
}
],
"title": "NOW() as time",
"transformations": [
{
"id": "merge",
"options": {
"reducers": []
}
}
],
"transform": "table",
"type": "table"
},
{
@@ -49,44 +49,26 @@
"type": "text"
},
{
"autoMigrateFrom": "singlestat",
"colorBackground": false,
"colorValue": false,
"colors": [
"#299c46",
"rgba(237, 129, 40, 0.89)",
"#d44a3a"
],
"datasource": {
"apiVersion": "v1",
"type": "grafana-testdata-datasource",
"uid": "testdata-type-uid"
},
"fieldConfig": {
"defaults": {
"color": {
"fixedColor": "rgb(31, 120, 193)",
"mode": "fixed"
},
"mappings": [
{
"options": {
"match": "null",
"result": {
"text": "N/A"
}
},
"type": "special"
}
],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
},
"unit": "none"
},
"overrides": []
"format": "none",
"gauge": {
"maxValue": 100,
"minValue": 0,
"show": false,
"thresholdLabels": false,
"thresholdMarkers": true
},
"gridPos": {
"h": 6,
@@ -102,25 +84,37 @@
"url": "d/-Y-tnEDWk/dashboard-tests-nested-template-variables?orgId=1\u0026${__all_variables}\u0026${__url_time_range}"
}
],
"maxDataPoints": 100,
"options": {
"colorMode": "none",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "horizontal",
"percentChangeColorMode": "standard",
"reduceOptions": {
"calcs": [
"mean"
],
"fields": "",
"values": false
"mappingType": 1,
"mappingTypes": [
{
"name": "value to text",
"value": 1
},
"showPercentChange": false,
"textMode": "auto",
"wideLayout": true
{
"name": "range to text",
"value": 2
}
],
"maxDataPoints": 100,
"nullPointMode": "connected",
"postfix": "",
"postfixFontSize": "50%",
"prefix": "",
"prefixFontSize": "50%",
"rangeMaps": [
{
"from": "null",
"text": "N/A",
"to": "null"
}
],
"sparkline": {
"fillColor": "rgba(31, 118, 189, 0.18)",
"full": false,
"lineColor": "rgb(31, 120, 193)",
"show": true
},
"pluginVersion": "12.1.0",
"tableColumn": "",
"targets": [
{
"datasource": {
@@ -132,8 +126,18 @@
"scenarioId": "random_walk"
}
],
"thresholds": "",
"title": "Panel drilldown link test",
"type": "stat"
"type": "stat",
"valueFontSize": "80%",
"valueMaps": [
{
"op": "=",
"text": "N/A",
"value": "null"
}
],
"valueName": "avg"
},
{
"aliasColors": {},
@@ -55,43 +55,31 @@
"type": "text"
},
{
"autoMigrateFrom": "singlestat",
"colorBackground": false,
"colorValue": false,
"colors": [
"#299c46",
"rgba(237, 129, 40, 0.89)",
"#d44a3a"
],
"datasource": {
"type": "grafana-testdata-datasource"
},
"fieldConfig": {
"defaults": {
"color": {
"fixedColor": "rgb(31, 120, 193)",
"mode": "fixed"
},
"mappings": [
{
"options": {
"match": "null",
"result": {
"text": "N/A"
}
},
"type": "special"
}
],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
},
"unit": "none"
"custom": {}
},
"overrides": []
},
"format": "none",
"gauge": {
"maxValue": 100,
"minValue": 0,
"show": false,
"thresholdLabels": false,
"thresholdMarkers": true
},
"gridPos": {
"h": 9,
"w": 4,
@@ -106,25 +94,28 @@
"url": "d/O6GmNPvWk/dashboard-tests-nested-template-variables-drilldown?orgId=1\u0026${__all_variables}\u0026${__url_time_range}"
}
],
"mappingType": 1,
"mappingTypes": [],
"maxDataPoints": 100,
"options": {
"colorMode": "none",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "horizontal",
"percentChangeColorMode": "standard",
"reduceOptions": {
"calcs": [
"mean"
],
"fields": "",
"values": false
},
"showPercentChange": false,
"textMode": "auto",
"wideLayout": true
"nullPointMode": "connected",
"postfix": "",
"postfixFontSize": "50%",
"prefix": "",
"prefixFontSize": "50%",
"rangeMaps": [
{
"from": "null",
"text": "N/A",
"to": "null"
}
],
"sparkline": {
"fillColor": "rgba(31, 118, 189, 0.18)",
"full": false,
"lineColor": "rgb(31, 120, 193)",
"show": true
},
"pluginVersion": "12.1.0",
"tableColumn": "",
"targets": [
{
"datasource": {
@@ -134,8 +125,18 @@
"scenarioId": "random_walk"
}
],
"thresholds": "",
"title": "Panel drilldown link test",
"type": "stat"
"type": "stat",
"valueFontSize": "80%",
"valueMaps": [
{
"op": "=",
"text": "N/A",
"value": "null"
}
],
"valueName": "avg"
},
{
"datasource": {
@@ -191,38 +191,24 @@
"type": "bargauge"
},
{
"autoMigrateFrom": "singlestat",
"colorBackground": false,
"colorValue": true,
"colors": [
"#299c46",
"#73BF69",
"#d44a3a"
],
"datasource": {
"type": "grafana-testdata-datasource"
},
"fieldConfig": {
"defaults": {
"mappings": [
{
"options": {
"match": "null",
"result": {
"text": "N/A"
}
},
"type": "special"
}
],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
},
"unit": "ms"
},
"overrides": []
"format": "ms",
"gauge": {
"maxValue": 100,
"minValue": 0,
"show": false,
"thresholdLabels": false,
"thresholdMarkers": true
},
"gridPos": {
"h": 4,
@@ -231,25 +217,38 @@
"y": 7
},
"id": 20,
"maxDataPoints": 100,
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "horizontal",
"percentChangeColorMode": "standard",
"reduceOptions": {
"calcs": [
"mean"
],
"fields": "",
"values": false
"mappingType": 1,
"mappingTypes": [
{
"name": "value to text",
"value": 1
},
"showPercentChange": false,
"textMode": "auto",
"wideLayout": true
{
"name": "range to text",
"value": 2
}
],
"maxDataPoints": 100,
"nullPointMode": "connected",
"pluginVersion": "6.2.0-pre",
"postfix": "",
"postfixFontSize": "50%",
"prefix": "p99",
"prefixFontSize": "50%",
"rangeMaps": [
{
"from": "null",
"text": "N/A",
"to": "null"
}
],
"sparkline": {
"fillColor": "rgba(31, 118, 189, 0.18)",
"full": false,
"lineColor": "rgb(31, 120, 193)",
"show": true
},
"pluginVersion": "12.1.0",
"tableColumn": "",
"targets": [
{
"datasource": {
@@ -259,41 +258,37 @@
"scenarioId": "random_walk"
}
],
"type": "stat"
"thresholds": "",
"type": "stat",
"valueFontSize": "120%",
"valueMaps": [
{
"op": "=",
"text": "N/A",
"value": "null"
}
],
"valueName": "avg"
},
{
"autoMigrateFrom": "singlestat",
"colorBackground": false,
"colorValue": true,
"colors": [
"#299c46",
"#73BF69",
"#d44a3a"
],
"datasource": {
"type": "grafana-testdata-datasource"
},
"fieldConfig": {
"defaults": {
"mappings": [
{
"options": {
"match": "null",
"result": {
"text": "N/A"
}
},
"type": "special"
}
],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
},
"unit": "ms"
},
"overrides": []
"format": "ms",
"gauge": {
"maxValue": 100,
"minValue": 0,
"show": false,
"thresholdLabels": false,
"thresholdMarkers": true
},
"gridPos": {
"h": 4,
@@ -302,25 +297,38 @@
"y": 7
},
"id": 23,
"maxDataPoints": 100,
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "horizontal",
"percentChangeColorMode": "standard",
"reduceOptions": {
"calcs": [
"mean"
],
"fields": "",
"values": false
"mappingType": 1,
"mappingTypes": [
{
"name": "value to text",
"value": 1
},
"showPercentChange": false,
"textMode": "auto",
"wideLayout": true
{
"name": "range to text",
"value": 2
}
],
"maxDataPoints": 100,
"nullPointMode": "connected",
"pluginVersion": "6.2.0-pre",
"postfix": "",
"postfixFontSize": "50%",
"prefix": "p95",
"prefixFontSize": "80%",
"rangeMaps": [
{
"from": "null",
"text": "N/A",
"to": "null"
}
],
"sparkline": {
"fillColor": "rgba(31, 118, 189, 0.18)",
"full": false,
"lineColor": "rgb(31, 120, 193)",
"show": true
},
"pluginVersion": "12.1.0",
"tableColumn": "",
"targets": [
{
"datasource": {
@@ -330,41 +338,37 @@
"scenarioId": "random_walk"
}
],
"type": "stat"
"thresholds": "",
"type": "stat",
"valueFontSize": "120%",
"valueMaps": [
{
"op": "=",
"text": "N/A",
"value": "null"
}
],
"valueName": "avg"
},
{
"autoMigrateFrom": "singlestat",
"colorBackground": false,
"colorValue": true,
"colors": [
"#299c46",
"#73BF69",
"#d44a3a"
],
"datasource": {
"type": "grafana-testdata-datasource"
},
"fieldConfig": {
"defaults": {
"mappings": [
{
"options": {
"match": "null",
"result": {
"text": "N/A"
}
},
"type": "special"
}
],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
},
"unit": "ms"
},
"overrides": []
"format": "ms",
"gauge": {
"maxValue": 100,
"minValue": 0,
"show": false,
"thresholdLabels": false,
"thresholdMarkers": true
},
"gridPos": {
"h": 4,
@@ -373,25 +377,38 @@
"y": 7
},
"id": 24,
"maxDataPoints": 100,
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "horizontal",
"percentChangeColorMode": "standard",
"reduceOptions": {
"calcs": [
"mean"
],
"fields": "",
"values": false
"mappingType": 1,
"mappingTypes": [
{
"name": "value to text",
"value": 1
},
"showPercentChange": false,
"textMode": "auto",
"wideLayout": true
{
"name": "range to text",
"value": 2
}
],
"maxDataPoints": 100,
"nullPointMode": "connected",
"pluginVersion": "6.2.0-pre",
"postfix": "",
"postfixFontSize": "50%",
"prefix": "p90",
"prefixFontSize": "80%",
"rangeMaps": [
{
"from": "null",
"text": "N/A",
"to": "null"
}
],
"sparkline": {
"fillColor": "rgba(31, 118, 189, 0.18)",
"full": false,
"lineColor": "rgb(31, 120, 193)",
"show": true
},
"pluginVersion": "12.1.0",
"tableColumn": "",
"targets": [
{
"datasource": {
@@ -401,41 +418,37 @@
"scenarioId": "random_walk"
}
],
"type": "stat"
"thresholds": "",
"type": "stat",
"valueFontSize": "120%",
"valueMaps": [
{
"op": "=",
"text": "N/A",
"value": "null"
}
],
"valueName": "avg"
},
{
"autoMigrateFrom": "singlestat",
"colorBackground": false,
"colorValue": true,
"colors": [
"#299c46",
"#73BF69",
"#d44a3a"
],
"datasource": {
"type": "grafana-testdata-datasource"
},
"fieldConfig": {
"defaults": {
"mappings": [
{
"options": {
"match": "null",
"result": {
"text": "N/A"
}
},
"type": "special"
}
],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
},
"unit": "ms"
},
"overrides": []
"format": "ms",
"gauge": {
"maxValue": 100,
"minValue": 0,
"show": false,
"thresholdLabels": false,
"thresholdMarkers": true
},
"gridPos": {
"h": 4,
@@ -444,25 +457,38 @@
"y": 7
},
"id": 45,
"maxDataPoints": 100,
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "horizontal",
"percentChangeColorMode": "standard",
"reduceOptions": {
"calcs": [
"mean"
],
"fields": "",
"values": false
"mappingType": 1,
"mappingTypes": [
{
"name": "value to text",
"value": 1
},
"showPercentChange": false,
"textMode": "auto",
"wideLayout": true
{
"name": "range to text",
"value": 2
}
],
"maxDataPoints": 100,
"nullPointMode": "connected",
"pluginVersion": "6.2.0-pre",
"postfix": "",
"postfixFontSize": "50%",
"prefix": "p90",
"prefixFontSize": "80%",
"rangeMaps": [
{
"from": "null",
"text": "N/A",
"to": "null"
}
],
"sparkline": {
"fillColor": "rgba(31, 118, 189, 0.18)",
"full": false,
"lineColor": "rgb(31, 120, 193)",
"show": true
},
"pluginVersion": "12.1.0",
"tableColumn": "",
"targets": [
{
"datasource": {
@@ -472,7 +498,17 @@
"scenarioId": "random_walk"
}
],
"type": "stat"
"thresholds": "",
"type": "stat",
"valueFontSize": "120%",
"valueMaps": [
{
"op": "=",
"text": "N/A",
"value": "null"
}
],
"valueName": "avg"
},
{
"aliasColors": {
@@ -20,45 +20,27 @@
"links": [],
"panels": [
{
"autoMigrateFrom": "singlestat",
"colorBackground": false,
"colorValue": false,
"colors": [
"#299c46",
"#5794F2",
"#d44a3a"
],
"datasource": {
"apiVersion": "v1",
"type": "grafana-testdata-datasource",
"uid": "testdata-type-uid"
},
"description": "asdasdas",
"fieldConfig": {
"defaults": {
"color": {
"fixedColor": "rgb(31, 120, 193)",
"mode": "fixed"
},
"mappings": [
{
"options": {
"match": "null",
"result": {
"text": "N/A"
}
},
"type": "special"
}
],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
},
"unit": "ms"
},
"overrides": []
"format": "ms",
"gauge": {
"maxValue": 100,
"minValue": 0,
"show": false,
"thresholdLabels": false,
"thresholdMarkers": true
},
"gridPos": {
"h": 4,
@@ -67,25 +49,37 @@
"y": 0
},
"id": 8,
"maxDataPoints": 100,
"options": {
"colorMode": "none",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "horizontal",
"percentChangeColorMode": "standard",
"reduceOptions": {
"calcs": [
"mean"
],
"fields": "",
"values": false
"mappingType": 1,
"mappingTypes": [
{
"name": "value to text",
"value": 1
},
"showPercentChange": false,
"textMode": "auto",
"wideLayout": true
{
"name": "range to text",
"value": 2
}
],
"maxDataPoints": 100,
"nullPointMode": "connected",
"postfix": "",
"postfixFontSize": "50%",
"prefix": "",
"prefixFontSize": "100%",
"rangeMaps": [
{
"from": "null",
"text": "N/A",
"to": "null"
}
],
"sparkline": {
"fillColor": "rgba(31, 118, 189, 0.18)",
"full": true,
"lineColor": "rgb(31, 120, 193)",
"show": true
},
"pluginVersion": "12.1.0",
"tableColumn": "",
"targets": [
{
"datasource": {
@@ -97,50 +91,42 @@
"scenarioId": "random_walk"
}
],
"thresholds": "",
"timeShift": "2h",
"title": "Title",
"type": "stat"
"type": "stat",
"valueFontSize": "120%",
"valueMaps": [
{
"op": "=",
"text": "N/A",
"value": "null"
}
],
"valueName": "avg"
},
{
"autoMigrateFrom": "singlestat",
"colorBackground": false,
"colorValue": false,
"colors": [
"#299c46",
"#5794F2",
"#d44a3a"
],
"datasource": {
"apiVersion": "v1",
"type": "grafana-testdata-datasource",
"uid": "testdata-type-uid"
},
"description": "asdasdas",
"fieldConfig": {
"defaults": {
"color": {
"fixedColor": "rgb(31, 120, 193)",
"mode": "fixed"
},
"mappings": [
{
"options": {
"match": "null",
"result": {
"text": "N/A"
}
},
"type": "special"
}
],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
},
"unit": "ms"
},
"overrides": []
"format": "ms",
"gauge": {
"maxValue": 100,
"minValue": 0,
"show": false,
"thresholdLabels": false,
"thresholdMarkers": true
},
"gridPos": {
"h": 4,
@@ -149,25 +135,37 @@
"y": 0
},
"id": 2,
"maxDataPoints": 100,
"options": {
"colorMode": "none",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "horizontal",
"percentChangeColorMode": "standard",
"reduceOptions": {
"calcs": [
"mean"
],
"fields": "",
"values": false
"mappingType": 1,
"mappingTypes": [
{
"name": "value to text",
"value": 1
},
"showPercentChange": false,
"textMode": "auto",
"wideLayout": true
{
"name": "range to text",
"value": 2
}
],
"maxDataPoints": 100,
"nullPointMode": "connected",
"postfix": "",
"postfixFontSize": "50%",
"prefix": "",
"prefixFontSize": "100%",
"rangeMaps": [
{
"from": "null",
"text": "N/A",
"to": "null"
}
],
"sparkline": {
"fillColor": "rgba(31, 118, 189, 0.18)",
"full": true,
"lineColor": "rgb(31, 120, 193)",
"show": true
},
"pluginVersion": "12.1.0",
"tableColumn": "",
"targets": [
{
"datasource": {
@@ -179,49 +177,41 @@
"scenarioId": "random_walk"
}
],
"thresholds": "",
"timeShift": "2h",
"type": "stat"
"type": "stat",
"valueFontSize": "120%",
"valueMaps": [
{
"op": "=",
"text": "N/A",
"value": "null"
}
],
"valueName": "avg"
},
{
"autoMigrateFrom": "singlestat",
"colorBackground": false,
"colorValue": false,
"colors": [
"#299c46",
"#5794F2",
"#d44a3a"
],
"datasource": {
"apiVersion": "v1",
"type": "grafana-testdata-datasource",
"uid": "testdata-type-uid"
},
"description": "asdasdas",
"fieldConfig": {
"defaults": {
"color": {
"fixedColor": "rgb(31, 120, 193)",
"mode": "fixed"
},
"mappings": [
{
"options": {
"match": "null",
"result": {
"text": "N/A"
}
},
"type": "special"
}
],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
},
"unit": "ms"
},
"overrides": []
"format": "ms",
"gauge": {
"maxValue": 100,
"minValue": 0,
"show": false,
"thresholdLabels": false,
"thresholdMarkers": true
},
"gridPos": {
"h": 4,
@@ -230,25 +220,37 @@
"y": 4
},
"id": 4,
"maxDataPoints": 100,
"options": {
"colorMode": "none",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "horizontal",
"percentChangeColorMode": "standard",
"reduceOptions": {
"calcs": [
"mean"
],
"fields": "",
"values": false
"mappingType": 1,
"mappingTypes": [
{
"name": "value to text",
"value": 1
},
"showPercentChange": false,
"textMode": "auto",
"wideLayout": true
{
"name": "range to text",
"value": 2
}
],
"maxDataPoints": 100,
"nullPointMode": "connected",
"postfix": "",
"postfixFontSize": "50%",
"prefix": "",
"prefixFontSize": "100%",
"rangeMaps": [
{
"from": "null",
"text": "N/A",
"to": "null"
}
],
"sparkline": {
"fillColor": "rgba(31, 118, 189, 0.18)",
"full": true,
"lineColor": "rgb(31, 120, 193)",
"show": true
},
"pluginVersion": "12.1.0",
"tableColumn": "",
"targets": [
{
"datasource": {
@@ -260,9 +262,19 @@
"scenarioId": "random_walk"
}
],
"thresholds": "",
"timeShift": "2h",
"title": "Panel Title",
"type": "stat"
"type": "stat",
"valueFontSize": "120%",
"valueMaps": [
{
"op": "=",
"text": "N/A",
"value": "null"
}
],
"valueName": "avg"
},
{
"datasource": {
@@ -404,43 +416,27 @@
"type": "text"
},
{
"autoMigrateFrom": "singlestat",
"colorBackground": false,
"colorValue": false,
"colors": [
"#299c46",
"rgba(237, 129, 40, 0.89)",
"#d44a3a"
],
"datasource": {
"apiVersion": "v1",
"type": "grafana-testdata-datasource",
"uid": "testdata-type-uid"
},
"description": "asdasdas",
"fieldConfig": {
"defaults": {
"mappings": [
{
"options": {
"match": "null",
"result": {
"text": "N/A"
}
},
"type": "special"
}
],
"max": 100,
"min": 0,
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
},
"unit": "none"
},
"overrides": []
"format": "none",
"gauge": {
"maxValue": 100,
"minValue": 0,
"show": true,
"thresholdLabels": false,
"thresholdMarkers": true
},
"gridPos": {
"h": 4,
@@ -449,25 +445,38 @@
"y": 8
},
"id": 10,
"maxDataPoints": 100,
"options": {
"colorMode": "none",
"graphMode": "none",
"justifyMode": "auto",
"orientation": "horizontal",
"percentChangeColorMode": "standard",
"reduceOptions": {
"calcs": [
"mean"
],
"fields": "",
"values": false
"mappingType": 1,
"mappingTypes": [
{
"name": "value to text",
"value": 1
},
"showPercentChange": false,
"textMode": "auto",
"wideLayout": true
{
"name": "range to text",
"value": 2
}
],
"maxDataPoints": 100,
"nullPointMode": "connected",
"pluginVersion": "6.2.0-pre",
"postfix": "",
"postfixFontSize": "50%",
"prefix": "",
"prefixFontSize": "50%",
"rangeMaps": [
{
"from": "null",
"text": "N/A",
"to": "null"
}
],
"sparkline": {
"fillColor": "rgba(31, 118, 189, 0.18)",
"full": false,
"lineColor": "rgb(31, 120, 193)",
"show": false
},
"pluginVersion": "12.1.0",
"tableColumn": "",
"targets": [
{
"datasource": {
@@ -479,8 +488,18 @@
"scenarioId": "random_walk"
}
],
"thresholds": "",
"timeShift": "2h",
"type": "stat"
"type": "stat",
"valueFontSize": "80%",
"valueMaps": [
{
"op": "=",
"text": "N/A",
"value": "null"
}
],
"valueName": "avg"
},
{
"datasource": {
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,62 @@
{
"title": "V13 Graph Thresholds Migration Test",
"schemaVersion": 12,
"panels": [
{
"type": "graph",
"id": 1,
"title": "Graph with Line Thresholds",
"grid": {
"threshold1": 200,
"threshold2": 400,
"threshold1Color": "yellow",
"threshold2Color": "red",
"thresholdLine": true
}
},
{
"type": "graph",
"id": 2,
"title": "Graph with Fill Thresholds",
"grid": {
"threshold1": 100,
"threshold2": 300,
"threshold1Color": "green",
"threshold2Color": "blue",
"thresholdLine": false
}
},
{
"type": "graph",
"id": 3,
"title": "Graph with Single Threshold",
"grid": {
"threshold1": 150,
"threshold1Color": "orange",
"thresholdLine": true
}
},
{
"type": "graph",
"id": 4,
"title": "Graph with Existing Thresholds",
"thresholds": [
{
"value": 50,
"color": "purple",
"colorMode": "custom"
}
],
"grid": {
"threshold1": 200,
"threshold1Color": "yellow",
"thresholdLine": false
}
},
{
"type": "singlestat",
"id": 5,
"title": "Non-Graph Panel"
}
]
}
@@ -0,0 +1,34 @@
{
"title": "Dashboard with minimal graph panel settings",
"tags": [],
"timezone": "browser",
"editable": true,
"panels": [
{
"id": 4,
"type": "graph",
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "gdev-prometheus"
},
"editorMode": "builder",
"expr": "{\"a.utf8.metric 🤘\", job=\"prometheus-utf8\"}",
"instant": false,
"legendFormat": "__auto",
"range": true,
"refId": "A"
}
]
}
],
"schemaVersion": 12,
"time": {
"from": "now-6h",
"to": "now"
},
"templating": {
"list": []
}
}
@@ -0,0 +1,174 @@
{
"annotations": {
"list": [
{
"builtIn": 1,
"datasource": {
"type": "grafana",
"uid": "-- Grafana --"
},
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations \u0026 Alerts",
"type": "dashboard"
}
]
},
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 0,
"links": [],
"panels": [
{
"autoMigrateFrom": "graph",
"datasource": {
"apiVersion": "v1",
"type": "prometheus",
"uid": "default-ds-uid"
},
"grid": {
"threshold1": 200,
"threshold1Color": "yellow",
"threshold2": 400,
"threshold2Color": "red",
"thresholdLine": true
},
"id": 1,
"targets": [
{
"datasource": {
"apiVersion": "v1",
"type": "prometheus",
"uid": "default-ds-uid"
},
"refId": "A"
}
],
"title": "Graph with Line Thresholds",
"type": "timeseries"
},
{
"autoMigrateFrom": "graph",
"datasource": {
"apiVersion": "v1",
"type": "prometheus",
"uid": "default-ds-uid"
},
"grid": {
"threshold1": 100,
"threshold1Color": "green",
"threshold2": 300,
"threshold2Color": "blue",
"thresholdLine": false
},
"id": 2,
"targets": [
{
"datasource": {
"apiVersion": "v1",
"type": "prometheus",
"uid": "default-ds-uid"
},
"refId": "A"
}
],
"title": "Graph with Fill Thresholds",
"type": "timeseries"
},
{
"autoMigrateFrom": "graph",
"datasource": {
"apiVersion": "v1",
"type": "prometheus",
"uid": "default-ds-uid"
},
"grid": {
"threshold1": 150,
"threshold1Color": "orange",
"thresholdLine": true
},
"id": 3,
"targets": [
{
"datasource": {
"apiVersion": "v1",
"type": "prometheus",
"uid": "default-ds-uid"
},
"refId": "A"
}
],
"title": "Graph with Single Threshold",
"type": "timeseries"
},
{
"autoMigrateFrom": "graph",
"datasource": {
"apiVersion": "v1",
"type": "prometheus",
"uid": "default-ds-uid"
},
"grid": {
"threshold1": 200,
"threshold1Color": "yellow",
"thresholdLine": false
},
"id": 4,
"targets": [
{
"datasource": {
"apiVersion": "v1",
"type": "prometheus",
"uid": "default-ds-uid"
},
"refId": "A"
}
],
"thresholds": [
{
"color": "purple",
"colorMode": "custom",
"value": 50
}
],
"title": "Graph with Existing Thresholds",
"type": "timeseries"
},
{
"autoMigrateFrom": "singlestat",
"datasource": {
"apiVersion": "v1",
"type": "prometheus",
"uid": "default-ds-uid"
},
"id": 5,
"targets": [
{
"datasource": {
"apiVersion": "v1",
"type": "prometheus",
"uid": "default-ds-uid"
},
"refId": "A"
}
],
"title": "Non-Graph Panel",
"type": "stat"
}
],
"refresh": "",
"schemaVersion": 42,
"tags": [],
"templating": {
"list": []
},
"time": {
"from": "now-6h",
"to": "now"
},
"timepicker": {},
"timezone": "",
"title": "V13 Graph Thresholds Migration Test",
"weekStart": ""
}
@@ -0,0 +1,61 @@
{
"annotations": {
"list": [
{
"builtIn": 1,
"datasource": {
"type": "grafana",
"uid": "-- Grafana --"
},
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations \u0026 Alerts",
"type": "dashboard"
}
]
},
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 0,
"links": [],
"panels": [
{
"autoMigrateFrom": "graph",
"datasource": {
"type": "prometheus",
"uid": "gdev-prometheus"
},
"id": 4,
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "gdev-prometheus"
},
"editorMode": "builder",
"expr": "{\"a.utf8.metric 🤘\", job=\"prometheus-utf8\"}",
"instant": false,
"legendFormat": "__auto",
"range": true,
"refId": "A"
}
],
"type": "timeseries"
}
],
"refresh": "",
"schemaVersion": 42,
"tags": [],
"templating": {
"list": []
},
"time": {
"from": "now-6h",
"to": "now"
},
"timepicker": {},
"timezone": "browser",
"title": "Dashboard with minimal graph panel settings",
"weekStart": ""
}
@@ -54,59 +54,13 @@
]
},
{
"autoMigrateFrom": "singlestat",
"datasource": {
"apiVersion": "v1",
"type": "prometheus",
"uid": "default-ds-uid"
},
"fieldConfig": {
"defaults": {
"mappings": [
{
"options": {
"match": "null",
"result": {
"text": "N/A"
}
},
"type": "special"
}
],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
}
},
"overrides": []
},
"id": 2,
"options": {
"colorMode": "none",
"graphMode": "none",
"justifyMode": "auto",
"orientation": "horizontal",
"percentChangeColorMode": "standard",
"reduceOptions": {
"calcs": [
"mean"
],
"fields": "",
"values": false
},
"showPercentChange": false,
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "12.1.0",
"targets": [
{
"datasource": {
@@ -118,7 +72,14 @@
}
],
"title": "Memory Usage",
"type": "stat"
"type": "stat",
"valueMaps": [
{
"op": "=",
"text": "N/A",
"value": "null"
}
]
}
],
"refresh": "",
@@ -75,30 +75,12 @@
"type": "timeseries"
},
{
"autoMigrateFrom": "singlestat",
"datasource": {
"apiVersion": "v1",
"type": "prometheus",
"uid": "default-ds-uid"
},
"fieldConfig": {
"defaults": {
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 7,
"w": 12,
@@ -106,24 +88,6 @@
"y": 1
},
"id": 2,
"options": {
"colorMode": "none",
"graphMode": "none",
"justifyMode": "auto",
"orientation": "horizontal",
"percentChangeColorMode": "standard",
"reduceOptions": {
"calcs": [
"mean"
],
"fields": "",
"values": false
},
"showPercentChange": false,
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "12.1.0",
"targets": [
{
"datasource": {
@@ -77,30 +77,12 @@
"type": "timeseries"
},
{
"autoMigrateFrom": "singlestat",
"datasource": {
"apiVersion": "v1",
"type": "prometheus",
"uid": "default-ds-uid"
},
"fieldConfig": {
"defaults": {
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 4,
"w": 6,
@@ -108,24 +90,7 @@
"y": 8
},
"id": 2,
"options": {
"colorMode": "none",
"graphMode": "none",
"justifyMode": "auto",
"orientation": "horizontal",
"percentChangeColorMode": "standard",
"reduceOptions": {
"calcs": [
"mean"
],
"fields": "",
"values": false
},
"showPercentChange": false,
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "12.1.0",
"maxPerRow": 6,
"targets": [
{
"datasource": {
@@ -153,49 +153,13 @@
"type": "gauge"
},
{
"autoMigrateFrom": "singlestat",
"datasource": {
"apiVersion": "v1",
"type": "prometheus",
"uid": "default-ds-uid"
},
"fieldConfig": {
"defaults": {
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
}
},
"overrides": []
},
"id": 6,
"options": {
"colorMode": "none",
"graphMode": "none",
"justifyMode": "auto",
"orientation": "horizontal",
"percentChangeColorMode": "standard",
"reduceOptions": {
"calcs": [
"mean"
],
"fields": "",
"values": false
},
"showPercentChange": false,
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "12.1.0",
"targets": [
{
"datasource": {
@@ -21,69 +21,25 @@
"links": [],
"panels": [
{
"autoMigrateFrom": "table-old",
"datasource": {
"apiVersion": "v1",
"type": "prometheus",
"uid": "default-ds-uid"
},
"fieldConfig": {
"defaults": {
"custom": {
"align": "auto",
"cellOptions": {
"type": "auto"
},
"footer": {
"reducers": []
},
"inspect": false
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
}
},
"overrides": [
{
"matcher": {
"id": "byName",
"options": "Time"
},
"properties": [
{
"id": "custom.align"
}
]
},
{
"matcher": {
"id": "byName",
"options": "Value"
},
"properties": [
{
"id": "custom.align"
}
]
}
]
},
"id": 1,
"options": {
"cellHeight": "sm",
"showHeader": true
},
"pluginVersion": "12.1.0",
"styles": [
{
"align": "auto",
"pattern": "Time",
"type": "number"
},
{
"align": "auto",
"pattern": "Value",
"type": "string"
}
],
"targets": [
{
"datasource": {
File diff suppressed because it is too large Load Diff
@@ -21,53 +21,23 @@
"links": [],
"panels": [
{
"autoMigrateFrom": "singlestat",
"colors": [
"#FF0000",
"green",
"orange"
],
"datasource": {
"apiVersion": "v1",
"type": "prometheus",
"uid": "default-ds-uid"
},
"fieldConfig": {
"defaults": {
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "#FF0000",
"value": null
},
{
"color": "green",
"value": 10
},
{
"color": "orange",
"value": 20
}
]
}
},
"overrides": []
"grid": {
"max": 10,
"min": 1
},
"id": 1,
"options": {
"colorMode": "none",
"graphMode": "none",
"justifyMode": "auto",
"orientation": "horizontal",
"percentChangeColorMode": "standard",
"reduceOptions": {
"calcs": [
"mean"
],
"fields": "",
"values": false
},
"showPercentChange": false,
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "12.1.0",
"legend": true,
"targets": [
{
"datasource": {
@@ -86,56 +56,31 @@
"refId": "B"
}
],
"thresholds": "10,20,30",
"type": "stat"
},
{
"autoMigrateFrom": "singlestat",
"colors": [
"#FF0000",
"green",
"orange"
],
"datasource": {
"apiVersion": "v1",
"type": "prometheus",
"uid": "default-ds-uid"
},
"fieldConfig": {
"defaults": {
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "#FF0000",
"value": null
},
{
"color": "green",
"value": 10
},
{
"color": "orange",
"value": 20
}
]
}
},
"overrides": []
"gauge": {
"show": true,
"thresholdLabels": false,
"thresholdMarkers": true
},
"grid": {
"max": 10,
"min": 1
},
"id": 2,
"options": {
"colorMode": "none",
"graphMode": "none",
"justifyMode": "auto",
"orientation": "horizontal",
"percentChangeColorMode": "standard",
"reduceOptions": {
"calcs": [
"mean"
],
"fields": "",
"values": false
},
"showPercentChange": false,
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "12.1.0",
"targets": [
{
"datasource": {
@@ -146,82 +91,33 @@
"refId": "A"
}
],
"thresholds": "10,20,30",
"type": "stat"
},
{
"autoMigrateFrom": "singlestat",
"colors": [
"#FF0000",
"green",
"orange"
],
"datasource": {
"apiVersion": "v1",
"type": "prometheus",
"uid": "default-ds-uid"
},
"fieldConfig": {
"defaults": {
"mappings": [
{
"options": {
"20": {
"text": "test"
}
},
"type": "value"
},
{
"options": {
"30": {
"text": "test1"
}
},
"type": "value"
},
{
"options": {
"40": {
"color": "orange",
"text": "50"
}
},
"type": "value"
}
],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "#FF0000",
"value": null
},
{
"color": "green",
"value": 10
},
{
"color": "orange",
"value": 20
}
]
}
},
"overrides": []
"grid": {
"max": 10,
"min": 1
},
"id": 3,
"options": {
"colorMode": "none",
"graphMode": "none",
"justifyMode": "auto",
"orientation": "horizontal",
"percentChangeColorMode": "standard",
"reduceOptions": {
"calcs": [
"mean"
],
"fields": "",
"values": false
},
"showPercentChange": false,
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "12.1.0",
"legend": true,
"mappingTypes": [
{
"name": "value to text",
"value": 1
}
],
"targets": [
{
"datasource": {
@@ -240,7 +136,25 @@
"refId": "B"
}
],
"type": "stat"
"thresholds": "10,20,30",
"type": "stat",
"valueMaps": [
{
"op": "=",
"text": "test",
"value": "20"
},
{
"op": "=",
"text": "test1",
"value": "30"
},
{
"op": "=",
"text": "50",
"value": "40"
}
]
},
{
"datasource": {
@@ -21,53 +21,23 @@
"links": [],
"panels": [
{
"autoMigrateFrom": "singlestat",
"colors": [
"#FF0000",
"green",
"orange"
],
"datasource": {
"apiVersion": "v1",
"type": "prometheus",
"uid": "default-ds-uid"
},
"fieldConfig": {
"defaults": {
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "#FF0000",
"value": null
},
{
"color": "green",
"value": 10
},
{
"color": "orange",
"value": 20
}
]
}
},
"overrides": []
"grid": {
"max": 10,
"min": 1
},
"id": 1,
"options": {
"colorMode": "none",
"graphMode": "none",
"justifyMode": "auto",
"orientation": "horizontal",
"percentChangeColorMode": "standard",
"reduceOptions": {
"calcs": [
"mean"
],
"fields": "",
"values": false
},
"showPercentChange": false,
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "12.1.0",
"legend": true,
"targets": [
{
"datasource": {
@@ -86,52 +56,27 @@
"refId": "B"
}
],
"thresholds": "10,20,30",
"type": "stat"
},
{
"autoMigrateFrom": "singlestat",
"colors": [
"#FF0000",
"green",
"orange"
],
"datasource": {
"apiVersion": "v1",
"type": "prometheus",
"uid": "default-ds-uid"
},
"fieldConfig": {
"defaults": {
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
}
},
"overrides": []
"grid": {
"max": 10,
"min": 1
},
"id": 2,
"options": {
"colorMode": "none",
"graphMode": "none",
"justifyMode": "auto",
"orientation": "horizontal",
"percentChangeColorMode": "standard",
"reduceOptions": {
"calcs": [
"mean"
],
"fields": "",
"values": false
},
"showPercentChange": false,
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "12.1.0",
"legend": true,
"targets": [
{
"datasource": {
@@ -150,56 +95,31 @@
"refId": "B"
}
],
"thresholds": "",
"type": "stat"
},
{
"autoMigrateFrom": "singlestat",
"colors": [
"#FF0000",
"green",
"orange"
],
"datasource": {
"apiVersion": "v1",
"type": "prometheus",
"uid": "default-ds-uid"
},
"fieldConfig": {
"defaults": {
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "#FF0000",
"value": null
},
{
"color": "green",
"value": 10
},
{
"color": "orange",
"value": 20
}
]
}
},
"overrides": []
"gauge": {
"show": true,
"thresholdLabels": false,
"thresholdMarkers": true
},
"grid": {
"max": 10,
"min": 1
},
"id": 3,
"options": {
"colorMode": "none",
"graphMode": "none",
"justifyMode": "auto",
"orientation": "horizontal",
"percentChangeColorMode": "standard",
"reduceOptions": {
"calcs": [
"mean"
],
"fields": "",
"values": false
},
"showPercentChange": false,
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "12.1.0",
"targets": [
{
"datasource": {
@@ -210,82 +130,33 @@
"refId": "A"
}
],
"thresholds": "10,20,30",
"type": "stat"
},
{
"autoMigrateFrom": "singlestat",
"colors": [
"#FF0000",
"green",
"orange"
],
"datasource": {
"apiVersion": "v1",
"type": "prometheus",
"uid": "default-ds-uid"
},
"fieldConfig": {
"defaults": {
"mappings": [
{
"options": {
"20": {
"text": "test"
}
},
"type": "value"
},
{
"options": {
"30": {
"text": "test1"
}
},
"type": "value"
},
{
"options": {
"40": {
"color": "orange",
"text": "50"
}
},
"type": "value"
}
],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "#FF0000",
"value": null
},
{
"color": "green",
"value": 10
},
{
"color": "orange",
"value": 20
}
]
}
},
"overrides": []
"grid": {
"max": 10,
"min": 1
},
"id": 4,
"options": {
"colorMode": "none",
"graphMode": "none",
"justifyMode": "auto",
"orientation": "horizontal",
"percentChangeColorMode": "standard",
"reduceOptions": {
"calcs": [
"mean"
],
"fields": "",
"values": false
},
"showPercentChange": false,
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "12.1.0",
"legend": true,
"mappingTypes": [
{
"name": "value to text",
"value": 1
}
],
"targets": [
{
"datasource": {
@@ -304,7 +175,25 @@
"refId": "B"
}
],
"type": "stat"
"thresholds": "10,20,30",
"type": "stat",
"valueMaps": [
{
"op": "=",
"text": "test",
"value": "20"
},
{
"op": "=",
"text": "test1",
"value": "30"
},
{
"op": "=",
"text": "50",
"value": "40"
}
]
},
{
"datasource": {
@@ -327,38 +216,24 @@
"type": "timeseries"
},
{
"autoMigrateFrom": "grafana-singlestat-panel",
"colorBackground": false,
"colorValue": true,
"colors": [
"#299c46",
"rgba(237, 129, 40, 0.89)",
"#d44a3a"
],
"datasource": {
"type": "prometheus"
},
"fieldConfig": {
"defaults": {
"mappings": [
{
"options": {
"match": "null",
"result": {
"text": "N/A"
}
},
"type": "special"
}
],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
},
"unit": "areaF2"
},
"overrides": []
"format": "areaF2",
"gauge": {
"maxValue": 100,
"minValue": 0,
"show": false,
"thresholdLabels": false,
"thresholdMarkers": true
},
"gridPos": {
"h": 8,
@@ -367,25 +242,37 @@
"y": 43
},
"id": 5,
"maxDataPoints": 100,
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "horizontal",
"percentChangeColorMode": "standard",
"reduceOptions": {
"calcs": [
"mean"
],
"fields": "",
"values": false
"mappingType": 1,
"mappingTypes": [
{
"name": "value to text",
"value": 1
},
"showPercentChange": false,
"textMode": "auto",
"wideLayout": true
{
"name": "range to text",
"value": 2
}
],
"maxDataPoints": 100,
"nullPointMode": "connected",
"postfix": "b",
"postfixFontSize": "50%",
"prefix": "a",
"prefixFontSize": "50%",
"rangeMaps": [
{
"from": "null",
"text": "N/A",
"to": "null"
}
],
"sparkline": {
"fillColor": "rgba(31, 118, 189, 0.18)",
"full": false,
"lineColor": "rgb(31, 120, 193)",
"show": true
},
"pluginVersion": "12.1.0",
"tableColumn": "",
"targets": [
{
"datasource": {
@@ -395,8 +282,18 @@
"refId": "A"
}
],
"thresholds": "",
"title": "grafana-singlestat-panel",
"type": "stat"
"type": "stat",
"valueFontSize": "80%",
"valueMaps": [
{
"op": "=",
"text": "N/A",
"value": "null"
}
],
"valueName": "avg"
},
{
"datasource": {
@@ -0,0 +1,98 @@
{
"annotations": {
"list": [
{
"builtIn": 1,
"datasource": {
"type": "grafana",
"uid": "-- Grafana --"
},
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations \u0026 Alerts",
"type": "dashboard"
}
]
},
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 0,
"links": [],
"panels": [
{
"autoMigrateFrom": "graph",
"grid": {
"threshold1": 200,
"threshold1Color": "yellow",
"threshold2": 400,
"threshold2Color": "red",
"thresholdLine": true
},
"id": 1,
"title": "Graph with Line Thresholds",
"type": "timeseries"
},
{
"autoMigrateFrom": "graph",
"grid": {
"threshold1": 100,
"threshold1Color": "green",
"threshold2": 300,
"threshold2Color": "blue",
"thresholdLine": false
},
"id": 2,
"title": "Graph with Fill Thresholds",
"type": "timeseries"
},
{
"autoMigrateFrom": "graph",
"grid": {
"threshold1": 150,
"threshold1Color": "orange",
"thresholdLine": true
},
"id": 3,
"title": "Graph with Single Threshold",
"type": "timeseries"
},
{
"autoMigrateFrom": "graph",
"grid": {
"threshold1": 200,
"threshold1Color": "yellow",
"thresholdLine": false
},
"id": 4,
"thresholds": [
{
"color": "purple",
"colorMode": "custom",
"value": 50
}
],
"title": "Graph with Existing Thresholds",
"type": "timeseries"
},
{
"autoMigrateFrom": "singlestat",
"id": 5,
"title": "Non-Graph Panel",
"type": "stat"
}
],
"schemaVersion": 13,
"tags": [],
"templating": {
"list": []
},
"time": {
"from": "now-6h",
"to": "now"
},
"timepicker": {},
"timezone": "",
"title": "V13 Graph Thresholds Migration Test",
"weekStart": ""
}
@@ -0,0 +1,56 @@
{
"annotations": {
"list": [
{
"builtIn": 1,
"datasource": {
"type": "grafana",
"uid": "-- Grafana --"
},
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations \u0026 Alerts",
"type": "dashboard"
}
]
},
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 0,
"links": [],
"panels": [
{
"autoMigrateFrom": "graph",
"id": 4,
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "gdev-prometheus"
},
"editorMode": "builder",
"expr": "{\"a.utf8.metric 🤘\", job=\"prometheus-utf8\"}",
"instant": false,
"legendFormat": "__auto",
"range": true,
"refId": "A"
}
],
"type": "timeseries"
}
],
"schemaVersion": 13,
"tags": [],
"templating": {
"list": []
},
"time": {
"from": "now-6h",
"to": "now"
},
"timepicker": {},
"timezone": "browser",
"title": "Dashboard with minimal graph panel settings",
"weekStart": ""
}
File diff suppressed because it is too large Load Diff
@@ -21,48 +21,18 @@
"links": [],
"panels": [
{
"fieldConfig": {
"defaults": {
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "#FF0000",
"value": null
},
{
"color": "green",
"value": 10
},
{
"color": "orange",
"value": 20
}
]
}
},
"overrides": []
"autoMigrateFrom": "singlestat",
"colors": [
"#FF0000",
"green",
"orange"
],
"grid": {
"max": 10,
"min": 1
},
"id": 1,
"options": {
"colorMode": "none",
"graphMode": "none",
"justifyMode": "auto",
"orientation": "horizontal",
"percentChangeColorMode": "standard",
"reduceOptions": {
"calcs": [
"mean"
],
"fields": "",
"values": false
},
"showPercentChange": false,
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "12.1.0",
"legend": true,
"targets": [
{
"refId": "A"
@@ -71,122 +41,48 @@
"refId": "B"
}
],
"thresholds": "10,20,30",
"type": "stat"
},
{
"fieldConfig": {
"defaults": {
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "#FF0000",
"value": null
},
{
"color": "green",
"value": 10
},
{
"color": "orange",
"value": 20
}
]
}
},
"overrides": []
"autoMigrateFrom": "singlestat",
"colors": [
"#FF0000",
"green",
"orange"
],
"gauge": {
"show": true,
"thresholdLabels": false,
"thresholdMarkers": true
},
"grid": {
"max": 10,
"min": 1
},
"id": 2,
"options": {
"colorMode": "none",
"graphMode": "none",
"justifyMode": "auto",
"orientation": "horizontal",
"percentChangeColorMode": "standard",
"reduceOptions": {
"calcs": [
"mean"
],
"fields": "",
"values": false
},
"showPercentChange": false,
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "12.1.0",
"thresholds": "10,20,30",
"type": "stat"
},
{
"fieldConfig": {
"defaults": {
"mappings": [
{
"options": {
"20": {
"text": "test"
}
},
"type": "value"
},
{
"options": {
"30": {
"text": "test1"
}
},
"type": "value"
},
{
"options": {
"40": {
"color": "orange",
"text": "50"
}
},
"type": "value"
}
],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "#FF0000",
"value": null
},
{
"color": "green",
"value": 10
},
{
"color": "orange",
"value": 20
}
]
}
},
"overrides": []
"autoMigrateFrom": "singlestat",
"colors": [
"#FF0000",
"green",
"orange"
],
"grid": {
"max": 10,
"min": 1
},
"id": 3,
"options": {
"colorMode": "none",
"graphMode": "none",
"justifyMode": "auto",
"orientation": "horizontal",
"percentChangeColorMode": "standard",
"reduceOptions": {
"calcs": [
"mean"
],
"fields": "",
"values": false
},
"showPercentChange": false,
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "12.1.0",
"legend": true,
"mappingTypes": [
{
"name": "value to text",
"value": 1
}
],
"targets": [
{
"refId": "A"
@@ -195,7 +91,25 @@
"refId": "B"
}
],
"type": "stat"
"thresholds": "10,20,30",
"type": "stat",
"valueMaps": [
{
"op": "=",
"text": "test",
"value": "20"
},
{
"op": "=",
"text": "test1",
"value": "30"
},
{
"op": "=",
"text": "50",
"value": "40"
}
]
},
{
"id": 4,
@@ -21,48 +21,18 @@
"links": [],
"panels": [
{
"fieldConfig": {
"defaults": {
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "#FF0000",
"value": null
},
{
"color": "green",
"value": 10
},
{
"color": "orange",
"value": 20
}
]
}
},
"overrides": []
"autoMigrateFrom": "singlestat",
"colors": [
"#FF0000",
"green",
"orange"
],
"grid": {
"max": 10,
"min": 1
},
"id": 1,
"options": {
"colorMode": "none",
"graphMode": "none",
"justifyMode": "auto",
"orientation": "horizontal",
"percentChangeColorMode": "standard",
"reduceOptions": {
"calcs": [
"mean"
],
"fields": "",
"values": false
},
"showPercentChange": false,
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "12.1.0",
"legend": true,
"targets": [
{
"refId": "A"
@@ -71,47 +41,22 @@
"refId": "B"
}
],
"thresholds": "10,20,30",
"type": "stat"
},
{
"fieldConfig": {
"defaults": {
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
}
},
"overrides": []
"autoMigrateFrom": "singlestat",
"colors": [
"#FF0000",
"green",
"orange"
],
"grid": {
"max": 10,
"min": 1
},
"id": 2,
"options": {
"colorMode": "none",
"graphMode": "none",
"justifyMode": "auto",
"orientation": "horizontal",
"percentChangeColorMode": "standard",
"reduceOptions": {
"calcs": [
"mean"
],
"fields": "",
"values": false
},
"showPercentChange": false,
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "12.1.0",
"legend": true,
"targets": [
{
"refId": "A"
@@ -120,122 +65,48 @@
"refId": "B"
}
],
"thresholds": "",
"type": "stat"
},
{
"fieldConfig": {
"defaults": {
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "#FF0000",
"value": null
},
{
"color": "green",
"value": 10
},
{
"color": "orange",
"value": 20
}
]
}
},
"overrides": []
"autoMigrateFrom": "singlestat",
"colors": [
"#FF0000",
"green",
"orange"
],
"gauge": {
"show": true,
"thresholdLabels": false,
"thresholdMarkers": true
},
"grid": {
"max": 10,
"min": 1
},
"id": 3,
"options": {
"colorMode": "none",
"graphMode": "none",
"justifyMode": "auto",
"orientation": "horizontal",
"percentChangeColorMode": "standard",
"reduceOptions": {
"calcs": [
"mean"
],
"fields": "",
"values": false
},
"showPercentChange": false,
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "12.1.0",
"thresholds": "10,20,30",
"type": "stat"
},
{
"fieldConfig": {
"defaults": {
"mappings": [
{
"options": {
"20": {
"text": "test"
}
},
"type": "value"
},
{
"options": {
"30": {
"text": "test1"
}
},
"type": "value"
},
{
"options": {
"40": {
"color": "orange",
"text": "50"
}
},
"type": "value"
}
],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "#FF0000",
"value": null
},
{
"color": "green",
"value": 10
},
{
"color": "orange",
"value": 20
}
]
}
},
"overrides": []
"autoMigrateFrom": "singlestat",
"colors": [
"#FF0000",
"green",
"orange"
],
"grid": {
"max": 10,
"min": 1
},
"id": 4,
"options": {
"colorMode": "none",
"graphMode": "none",
"justifyMode": "auto",
"orientation": "horizontal",
"percentChangeColorMode": "standard",
"reduceOptions": {
"calcs": [
"mean"
],
"fields": "",
"values": false
},
"showPercentChange": false,
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "12.1.0",
"legend": true,
"mappingTypes": [
{
"name": "value to text",
"value": 1
}
],
"targets": [
{
"refId": "A"
@@ -244,7 +115,25 @@
"refId": "B"
}
],
"type": "stat"
"thresholds": "10,20,30",
"type": "stat",
"valueMaps": [
{
"op": "=",
"text": "test",
"value": "20"
},
{
"op": "=",
"text": "test1",
"value": "30"
},
{
"op": "=",
"text": "50",
"value": "40"
}
]
},
{
"id": 8,
@@ -257,38 +146,24 @@
"type": "timeseries"
},
{
"autoMigrateFrom": "grafana-singlestat-panel",
"colorBackground": false,
"colorValue": true,
"colors": [
"#299c46",
"rgba(237, 129, 40, 0.89)",
"#d44a3a"
],
"datasource": {
"type": "prometheus"
},
"fieldConfig": {
"defaults": {
"mappings": [
{
"options": {
"match": "null",
"result": {
"text": "N/A"
}
},
"type": "special"
}
],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
},
"unit": "areaF2"
},
"overrides": []
"format": "areaF2",
"gauge": {
"maxValue": 100,
"minValue": 0,
"show": false,
"thresholdLabels": false,
"thresholdMarkers": true
},
"gridPos": {
"h": 8,
@@ -297,25 +172,37 @@
"y": 43
},
"id": 5,
"maxDataPoints": 100,
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "horizontal",
"percentChangeColorMode": "standard",
"reduceOptions": {
"calcs": [
"mean"
],
"fields": "",
"values": false
"mappingType": 1,
"mappingTypes": [
{
"name": "value to text",
"value": 1
},
"showPercentChange": false,
"textMode": "auto",
"wideLayout": true
{
"name": "range to text",
"value": 2
}
],
"maxDataPoints": 100,
"nullPointMode": "connected",
"postfix": "b",
"postfixFontSize": "50%",
"prefix": "a",
"prefixFontSize": "50%",
"rangeMaps": [
{
"from": "null",
"text": "N/A",
"to": "null"
}
],
"sparkline": {
"fillColor": "rgba(31, 118, 189, 0.18)",
"full": false,
"lineColor": "rgb(31, 120, 193)",
"show": true
},
"pluginVersion": "12.1.0",
"tableColumn": "",
"targets": [
{
"datasource": {
@@ -325,8 +212,18 @@
"refId": "A"
}
],
"thresholds": "",
"title": "grafana-singlestat-panel",
"type": "stat"
"type": "stat",
"valueFontSize": "80%",
"valueMaps": [
{
"op": "=",
"text": "N/A",
"value": "null"
}
],
"valueName": "avg"
},
{
"datasource": {
+2 -2
View File
@@ -2541,10 +2541,10 @@
},
"public/app/features/dashboard/state/DashboardMigrator.ts": {
"@typescript-eslint/consistent-type-assertions": {
"count": 4
"count": 2
},
"@typescript-eslint/no-explicit-any": {
"count": 24
"count": 20
}
},
"public/app/features/dashboard/state/DashboardModel.repeat.test.ts": {
@@ -1,4 +1,4 @@
import { each, find, findIndex, flattenDeep, isArray, isBoolean, isNumber, isString, map, max, some } from 'lodash';
import { each, find, findIndex, flattenDeep, isArray, isBoolean, isString, map, max, some } from 'lodash';
import {
AnnotationQuery,
@@ -15,7 +15,6 @@ import {
isDataSourceRef,
isEmptyObject,
MappingType,
PanelPlugin,
ReducerID,
SpecialValueMatch,
standardEditorsRegistry,
@@ -33,7 +32,6 @@ import { DataTransformerConfig } from '@grafana/schema';
import { AxisPlacement, GraphFieldConfig } from '@grafana/ui';
import { migrateTableDisplayModeToCellOptions } from '@grafana/ui/internal';
import { getAllOptionEditors, getAllStandardFieldConfigs } from 'app/core/components/OptionsUI/registry';
import { config } from 'app/core/config';
import {
DEFAULT_PANEL_SPAN,
DEFAULT_ROW_HEIGHT,
@@ -53,8 +51,6 @@ import { isConstant, isMulti } from 'app/features/variables/guard';
import { alignCurrentWithMulti } from 'app/features/variables/shared/multiOptions';
import { CloudWatchMetricsQuery, LegacyAnnotationQuery } from 'app/plugins/datasource/cloudwatch/types';
import { MIXED_DATASOURCE_NAME } from 'app/plugins/datasource/mixed/MixedDataSource';
import { plugin as gaugePanelPlugin } from 'app/plugins/panel/gauge/module';
import { plugin as statPanelPlugin } from 'app/plugins/panel/stat/module';
import {
migrateCloudWatchQuery,
@@ -403,72 +399,8 @@ export class DashboardMigrator {
}
if (oldVersion < 13 && finalTargetVersion >= 13) {
// update graph yaxes changes
panelUpgrades.push((panel: any) => {
if (panel.type !== 'graph') {
return panel;
}
if (!panel.grid) {
return panel;
}
if (!panel.thresholds) {
panel.thresholds = [];
}
const t1: any = {},
t2: any = {};
if (panel.grid.threshold1 !== null) {
t1.value = panel.grid.threshold1;
if (panel.grid.thresholdLine) {
t1.line = true;
t1.lineColor = panel.grid.threshold1Color;
t1.colorMode = 'custom';
} else {
t1.fill = true;
t1.fillColor = panel.grid.threshold1Color;
t1.colorMode = 'custom';
}
}
if (panel.grid.threshold2 !== null) {
t2.value = panel.grid.threshold2;
if (panel.grid.thresholdLine) {
t2.line = true;
t2.lineColor = panel.grid.threshold2Color;
t2.colorMode = 'custom';
} else {
t2.fill = true;
t2.fillColor = panel.grid.threshold2Color;
t2.colorMode = 'custom';
}
}
if (isNumber(t1.value)) {
if (isNumber(t2.value)) {
if (t1.value > t2.value) {
t1.op = t2.op = 'lt';
panel.thresholds.push(t1);
panel.thresholds.push(t2);
} else {
t1.op = t2.op = 'gt';
panel.thresholds.push(t1);
panel.thresholds.push(t2);
}
} else {
t1.op = 'gt';
panel.thresholds.push(t1);
}
}
delete panel.grid.threshold1;
delete panel.grid.threshold1Color;
delete panel.grid.threshold2;
delete panel.grid.threshold2Color;
delete panel.grid.thresholdLine;
return panel;
});
// graph panel auto migrates to either barchart, bargauge, histogram or timeseries (all standard Grafana plugins)
// see public/app/features/dashboard/state/getPanelPluginToMigrateTo.ts
}
if (oldVersion < 14 && finalTargetVersion >= 14) {
@@ -693,14 +625,6 @@ export class DashboardMigrator {
}
if (oldVersion < 28 && finalTargetVersion >= 28) {
panelUpgrades.push((panel: PanelModel) => {
if (panel.type === 'singlestat') {
return migrateSinglestat(panel);
}
return panel;
});
for (const variable of this.dashboard.templating.list) {
if (variable.tags) {
delete variable.tags;
@@ -1265,40 +1189,6 @@ function updateVariablesSyntax(text: string) {
});
}
function migrateSinglestat(panel: PanelModel) {
// If 'grafana-singlestat-panel' exists, move to that
if (config.panels['grafana-singlestat-panel']) {
panel.type = 'grafana-singlestat-panel';
return panel;
}
let returnSaveModel = false;
if (!panel.changePlugin) {
returnSaveModel = true;
panel = new PanelModel(panel);
}
// To make sure PanelModel.isAngularPlugin logic thinks the current panel is angular
// And since this plugin no longer exist we just fake it here
panel.plugin = { angularPanelCtrl: {} } as PanelPlugin;
// Otheriwse use gauge or stat panel
if ((panel as any).gauge?.show) {
gaugePanelPlugin.meta = config.panels['gauge'];
panel.changePlugin(gaugePanelPlugin);
} else {
statPanelPlugin.meta = config.panels['stat'];
panel.changePlugin(statPanelPlugin);
}
if (returnSaveModel) {
return panel.getSaveModel();
}
return panel;
}
interface MigrateDatasourceNameOptions {
returnDefaultAsNull: boolean;
}
@@ -18,7 +18,6 @@ import {
getJsonInputFiles,
extractTargetVersionFromFilename,
constructBackendOutputFilename,
handleAngularPanelMigration,
} from './__tests__/migrationTestUtils';
/*
@@ -109,9 +108,6 @@ describe('Backend / Frontend single version migration result comparison', () =>
getVariablesFromState: () => jsonInput?.templating?.list ?? [],
});
// Handle angular panel migration if needed
await handleAngularPanelMigration(frontendModel, jsonInput.schemaVersion, targetVersion);
const frontendMigrationResult = frontendModel.getSaveModelClone();
// version in the backend is never added because it is returned from the backend as metadata
@@ -12,11 +12,7 @@ import { createTextBoxVariableAdapter } from 'app/features/variables/textbox/ada
import { DASHBOARD_SCHEMA_VERSION } from './DashboardMigrator';
import { DashboardModel } from './DashboardModel';
import {
setupDevDashboardDataSources,
handleAngularPanelMigration,
constructLatestVersionOutputFilename,
} from './__tests__/migrationTestUtils';
import { setupDevDashboardDataSources, constructLatestVersionOutputFilename } from './__tests__/migrationTestUtils';
/*
* Dev Dashboard Backend / Frontend Migration Comparison Test
@@ -107,9 +103,6 @@ describe('Dev Dashboard Backend / Frontend result comparison', () => {
getVariablesFromState: () => jsonInput?.templating?.list ?? [],
});
// Handle angular panel migration if needed
await handleAngularPanelMigration(frontendModel, jsonInput.schemaVersion, DASHBOARD_SCHEMA_VERSION);
const frontendMigrationResult = frontendModel.getSaveModelClone();
// version in the backend is never added because it is returned from the backend as metadata
@@ -17,7 +17,6 @@ import {
getOutputDirectory,
getJsonInputFiles,
constructLatestVersionOutputFilename,
handleAngularPanelMigration,
} from './__tests__/migrationTestUtils';
/*
@@ -76,9 +75,6 @@ describe('Backend / Frontend result comparison', () => {
getVariablesFromState: () => jsonInput?.templating?.list ?? [],
});
// Handle angular panel migration if needed
await handleAngularPanelMigration(frontendModel, jsonInput.schemaVersion, DASHBOARD_SCHEMA_VERSION);
const frontendMigrationResult = frontendModel.getSaveModelClone();
// version in the backend is never added because it is returned from the backend as metadata
@@ -1,14 +1,9 @@
import { readdirSync } from 'fs';
import path from 'path';
import { PanelPlugin } from '@grafana/data';
import { mockDataSource } from 'app/features/alerting/unified/mocks';
import { setupDataSources } from 'app/features/alerting/unified/testSetup/datasources';
import { MIXED_DATASOURCE_NAME } from 'app/plugins/datasource/mixed/MixedDataSource';
import { plugin as statPanelPlugin } from 'app/plugins/panel/stat/module';
import { plugin as tablePanelPlugin } from 'app/plugins/panel/table/module';
import { DashboardModel } from '../DashboardModel';
// Set up the same datasources as backend test provider to ensure consistency
export const dataSources = {
@@ -182,71 +177,3 @@ export function constructBackendOutputFilename(inputFile: string, targetVersion:
export function constructLatestVersionOutputFilename(inputFile: string, latestVersion: number): string {
return inputFile.replace('.json', `.v${latestVersion}.json`);
}
export const pluginVersionForAutoMigrate = '12.1.0';
/**
* Creates a type-compatible PanelPlugin wrapper for the real panel plugins
* This ensures the plugin has the correct version set and is compatible with pluginLoaded method
*/
function getPanelPlugin(pluginId: 'stat' | 'table'): PanelPlugin {
const realPlugin = pluginId === 'stat' ? statPanelPlugin : tablePanelPlugin;
// Create a copy of the plugin to avoid modifying the original
const pluginCopy = Object.create(Object.getPrototypeOf(realPlugin));
Object.assign(pluginCopy, realPlugin);
// Ensure meta and info exist
if (!pluginCopy.meta.info) {
pluginCopy.meta.info = {
author: { name: 'Grafana Labs', url: 'https://grafana.com' },
description: `${pluginId} panel plugin`,
links: [],
logos: { small: '', large: '' },
screenshots: [],
updated: '2024-01-01',
version: pluginVersionForAutoMigrate,
};
} else {
// Ensure version is set
pluginCopy.meta.info.version = pluginVersionForAutoMigrate;
}
return pluginCopy;
}
export async function handleAngularPanelMigration(
frontendModel: DashboardModel,
sourceVersion: number,
targetVersion: number
): Promise<void> {
/*
Migration from schema V27 involves migrating angular singlestat panels to stat panels
These panels are auto migrated where PanelModel.restoreModel() is called in the constructor,
and the autoMigrateFrom is set and type is set to "stat". So this logic will not run.
if (oldVersion < 28) {
panelUpgrades.push((panel: PanelModel) => {
if (panel.type === 'singlestat') {
return migrateSinglestat(panel);
}
});
}
Furthermore, the PanelModel.pluginLoaded is run in the old architecture through a redux action so it will not run in this test.
In the scenes architecture the angular migration logic runs through a migration handler inside transformSaveModelToScene.ts
_UNSAFE_customMigrationHandler: getAngularPanelMigrationHandler(panel),
We need to manually run the pluginLoaded logic to ensure the panels are migrated correctly.
which means that the actual migration logic is not run.
We need to manually run the pluginLoaded logic to ensure the panels are migrated correctly.
*/
for (const panel of frontendModel.panels) {
if (panel.type === 'stat' && panel.autoMigrateFrom && targetVersion >= 28 && sourceVersion < 28) {
const statPlugin = getPanelPlugin('stat');
await panel.pluginLoaded(statPlugin);
}
if (panel.type === 'table' && panel.autoMigrateFrom === 'table-old' && targetVersion >= 24 && sourceVersion < 24) {
const tablePlugin = getPanelPlugin('table');
await panel.pluginLoaded(tablePlugin);
}
}
}