# Dashboard migrations This document describes the Grafana dashboard migration system, focusing on conversion-level practices including metrics, logging, and testing infrastructure for API version conversions. For schema version migration implementation details, see the [SchemaVersion Migration Guide](./schemaversion/README.md). ## Table of Contents - [Overview](#overview) - [Conversion Flow](#conversion-flow-v0--v1--v2) - [v0 to v1 Conversion](#v0-to-v1-conversion) - [v1 to v2 Conversion](#v1-to-v2-conversion) - [v2 to v0/v1 Conversion](#v2-to-v0v1-conversion) - [Conversion Matrix](#conversion-matrix) - [API Versions](#api-versions) - [Schema Versions](#schema-versions) - [Testing](#testing) - [Backend conversion tests](#backend-conversion-tests) - [Backend and frontend conversion parity tests](#backend-and-frontend-conversion-parity-tests) - [Monitoring Migrations](#monitoring-migrations) - [Metrics](#metrics) - [Dashboard conversion success metric](#1-dashboard-conversion-success-metric) - [Dashboard conversion failure metric](#2-dashboard-conversion-failure-metric) - [Error Types](#error-types) - [Logging](#logging) - [Log structure](#log-structure) - [Log levels](#log-levels) - [Implementation Details](#implementation-details) - [Automatic instrumentation](#automatic-instrumentation) - [Error handling](#error-handling) - [Registration](#registration) - [Metrics registration](#metrics-registration) - [Available metrics](#available-metrics) - [Related Documentation](#related-documentation) ## Overview The Grafana dashboard migration system operates across three main conversion layers: ### Conversion Flow: v0 → v1 → v2 ``` v0alpha1 (Legacy JSON) → v1beta1 (Migrated JSON) → v2alpha1/v2beta1 (Structured) ``` #### v0 to v1 Conversion: - All schema migrations (v0-v42) are executed in the backend - Ports the logic from DashboardMigrator and implements built-in plugin migrations for panel plugins since backend cannot load plugins - Transforms legacy JSON dashboards to migrated JSON format - Handles backward compatibility for older dashboard formats - See [SchemaVersion Migration Guide](./schemaversion/README.md) for detailed instructions on creating new schema migrations #### v1 to v2 Conversion: - API version conversions between different Kubernetes API versions - Transforms JSON dashboards to structured dashboard format - v2 schema is the stable, typed schema with proper type definitions - Handles modern dashboard features and Kubernetes-native storage - Preserves Angular panel migration data for frontend processing (see [Angular Panel Migrations](#angular-panel-migrations)) - See [V2 to V1 Layout Conversion](./conversion/v2_to_v1_layout_conversion.md) for details on how V2 layouts are converted back to V1 panel arrays #### v2 to v0/v1 Conversion: - Converts structured v2 dashboards back to JSON format (v0alpha1 or v1beta1) - Chains through intermediate versions: v2 → v1beta1 → v0alpha1 - v0alpha1 and v1beta1 share the same spec structure (only API version differs) - Enables backward compatibility when storing v2 dashboards in legacy format ### Angular Panel Migrations When converting dashboards from v0/v1 to v2, panels with Angular types require special handling. The `autoMigrateFrom` field is used in v0 and v1 to indicate the panel was migrated from a deprecated plugin type, and the target plugin contains migration logic to transform the original panel's options and field configurations. Panel plugins define their own migration logic via `plugin.onPanelTypeChanged()`. This migration runs in the frontend when a panel type changes, transforming old options/fieldConfig to the new format. Examples: - `singlestat.format: "short"` → `stat.fieldConfig.defaults.unit: "short"` - `graph.legend.show: true` → `timeseries.options.legend.showLegend: true` The v2 schema doesn't include `autoMigrateFrom` as a typed field, so we need a mechanism to preserve the original panel data for the frontend to run these plugin migrations. #### `__angularMigration` Temporary Data The backend v1 → v2 conversion preserves the original panel data in a temporary field within `vizConfig.spec.options`. This works for **any** Angular panel, not just specific panel types: ```json { "vizConfig": { "kind": "stat", "spec": { "options": { "__angularMigration": { "autoMigrateFrom": "singlestat", "originalPanel": { "type": "singlestat", "format": "short", "colorBackground": true, "sparkline": { "show": true }, "fieldConfig": { ... }, "options": { ... } } } } } } } ``` #### How It Works 1. **Backend (v1 → v2):** The conversion detects panels that need Angular migration in two ways: - If `autoMigrateFrom` is already set on the panel (from v0 → v1 migration) - panel type already converted - If the panel type is a known Angular panel type (fallback for dashboards stored directly as v1 without v0 → v1 migration) In the second case, the conversion also transforms the panel type (e.g., `singlestat` → `stat`) and sets `autoMigrateFrom`. This replicates the same logic as the v0 → v1 migration. The entire original panel is stored under `options.__angularMigration` for the frontend to run plugin-specific migrations. 2. **Frontend (v2 load):** When building a `VizPanel` from v2 data: - Extracts `__angularMigration` from options - Removes it from the options object (not persisted) - Attaches a custom migration handler via `_UNSAFE_customMigrationHandler` 3. **Plugin load (VizPanel activation):** When the plugin loads, the migration handler calls `plugin.onPanelTypeChanged()` with the original panel data, allowing the plugin's own migration code to run #### Key Points - Works for **any** panel with `autoMigrateFrom`, not limited to specific panel types - Each plugin defines its own migration logic - the backend just preserves the data - The `originalPanel` contains the complete panel data to ensure no information is lost - Migration data is removed from options after loaded in frontend - The 0v → v1 and v1 → v2 conversions automatically detects these Angular panel types if `autoMigrateFrom` is not set. #### Implementation Files | File | Purpose | |------|---------| | `apps/dashboard/pkg/migration/conversion/v1beta1_to_v2alpha1.go` | Injects `__angularMigration` during v1 → v2 | | `public/app/features/dashboard-scene/serialization/layoutSerializers/utils.ts` | Extracts and consumes `__angularMigration` | | `public/app/features/dashboard-scene/serialization/angularMigration.ts` | Creates migration handler for v2 path | ## Conversion Matrix The system supports conversions between all dashboard API versions: | From ↓ / To → | v0alpha1 | v1beta1 | v2alpha1 | v2beta1 | |---------------|----------|---------|----------|---------| | **v0alpha1** | ✓ | ✓ | ✓ | ✓ | | **v1beta1** | ✓ | ✓ | ✓ | ✓ | | **v2alpha1** | ✓ | ✓ | ✓ | ✓ | | **v2beta1** | ✓ | ✓ | ✓ | ✓ | Each conversion path is automatically instrumented with metrics and logging. ## API Versions The supported dashboard API versions are: - `dashboard.grafana.app/v0alpha1` - Legacy JSON dashboard format - `dashboard.grafana.app/v1beta1` - Migrated JSON dashboard format - `dashboard.grafana.app/v2alpha1` - New structured dashboard format - `dashboard.grafana.app/v2beta1` - Enhanced structured dashboard format ## Schema Versions Schema versions (v13-v42) apply only to v0alpha1 and v1beta1 dashboards: - **Minimum supported version**: v13 - **Latest version**: v42 - **Migration path**: Sequential (v13→v14→v15...→v42) For detailed information about creating schema version migrations, see the [SchemaVersion Migration Guide](./schemaversion/README.md). ## Testing The implementation includes comprehensive test coverage for conversion-level operations: - **Backend conversion tests**: API version conversions with metrics validation - **Frontend tests**: TypeScript conversion tests - **Integration tests**: End-to-end conversion validation - **Metrics tests**: Prometheus counter validation ### Backend conversion tests The backend conversion tests validate API version conversions and metrics instrumentation: - **API conversion tests**: Test conversions between v0alpha1, v1beta1, v2alpha1, v2beta1 - **Metrics validation**: Tests verify that conversion metrics are properly recorded - **Error handling**: Tests validate error classification and logging - **Performance**: Tests ensure conversion operations are efficient **Test execution:** ```bash # All backend conversion tests go test ./apps/dashboard/pkg/migration/conversion/... -v # Metrics validation tests go test ./apps/dashboard/pkg/migration/... -run TestSchemaMigrationMetrics ``` ### Backend and frontend conversion parity tests These tests ensure that backend (Go) and frontend (TypeScript) conversions produce identical outputs. This is critical because: - **Dual implementation**: Both backend and frontend implement dashboard version conversions - **Consistency requirement**: Users should see the same dashboard regardless of which path is used - **API flexibility**: The API may return dashboards in different versions depending on context **Why normalize through Scene?** Both backend and frontend outputs are passed through the same Scene load/save cycle before comparison. This normalization: - Eliminates differences from default values added by Scene - Handles field ordering variations - Simulates the real-world flow: dashboard loaded → edited → saved **Test locations:** | Test File | Purpose | |-----------|---------| | `public/app/features/dashboard-scene/serialization/transformSaveModelV1ToV2.test.ts` | v1beta1 → v2beta1 conversion parity | | `public/app/features/dashboard-scene/serialization/transformSaveModelV2ToV1.test.ts` | v2beta1 → v1beta1/v0alpha1 conversion parity | | `public/app/features/dashboard/state/DashboardMigratorToBackend.test.ts` | Schema version migration parity | **Test execution:** ```bash # V1 to V2 conversion parity tests yarn test transformSaveModelV1ToV2.test.ts # V2 to V1 conversion parity tests yarn test transformSaveModelV2ToV1.test.ts # Schema migration parity tests yarn test DashboardMigratorToBackend.test.ts ``` **Test approach (v1 → v2):** - **Backend path**: `v1beta1 → Go conversion → v2beta1 → Scene → normalized output` - **Frontend path**: `v1beta1 → Scene → v2beta1 → Scene → normalized output` - **Test data**: Uses files from `apps/dashboard/pkg/migration/conversion/testdata/` and migrated dashboards **Test approach (v2 → v1/v0):** - **Backend path**: `v2beta1 → Go conversion → v1beta1/v0alpha1 → Scene → normalized output` - **Frontend path**: `v2beta1 → Scene → v1beta1 → Scene → normalized output` - **Target versions**: Tests both v0alpha1 and v1beta1 (they share the same spec structure) - **Test data**: Uses files from `apps/dashboard/pkg/migration/conversion/testdata/` For schema version migration testing details, see the [SchemaVersion Migration Guide](./schemaversion/README.md). ## Monitoring Migrations The dashboard migration system provides comprehensive observability through metrics, logging, and error classification to monitor conversion operations. ### Metrics The dashboard migration system now provides comprehensive observability through: - **Prometheus metrics** for tracking conversion success/failure rates and performance - **Structured logging** for debugging and monitoring conversion operations - **Automatic instrumentation** via wrapper functions that eliminate code duplication - **Error classification** to distinguish between different types of migration failures #### 1. Dashboard conversion success metric **Metric Name:** `grafana_dashboard_migration_conversion_success_total` **Type:** Counter **Description:** Total number of successful dashboard conversions **Labels:** - `source_version_api` - Source API version (e.g., "dashboard.grafana.app/v0alpha1") - `target_version_api` - Target API version (e.g., "dashboard.grafana.app/v1beta1") - `source_schema_version` - Source schema version (e.g., "16") - only for v0/v1 dashboards - `target_schema_version` - Target schema version (e.g., "41") - only for v0/v1 dashboards **Example:** ```prometheus grafana_dashboard_migration_conversion_success_total{ source_version_api="dashboard.grafana.app/v0alpha1", target_version_api="dashboard.grafana.app/v1beta1", source_schema_version="16", target_schema_version="41" } 1250 ``` #### 2. Dashboard conversion failure metric **Metric Name:** `grafana_dashboard_migration_conversion_failure_total` **Type:** Counter **Description:** Total number of failed dashboard conversions **Labels:** - `source_version_api` - Source API version - `target_version_api` - Target API version - `source_schema_version` - Source schema version (only for v0/v1 dashboards) - `target_schema_version` - Target schema version (only for v0/v1 dashboards) - `error_type` - Classification of the error (see Error Types section) **Example:** ```prometheus grafana_dashboard_migration_conversion_failure_total{ source_version_api="dashboard.grafana.app/v0alpha1", target_version_api="dashboard.grafana.app/v1beta1", source_schema_version="14", target_schema_version="41", error_type="schema_version_migration_error" } 42 ``` ### Error Types The `error_type` label classifies failures into four categories: #### 1. `conversion_error` - General conversion failures not related to schema migration - API-level conversion issues - Programming errors in conversion functions #### 2. `schema_version_migration_error` - Failures during individual schema version migrations (v14→v15, v15→v16, etc.) - Schema-specific transformation errors - Data format incompatibilities #### 3. `schema_minimum_version_error` - Dashboards with schema versions below the minimum supported version (< v13) - These are logged as warnings rather than errors - Indicates dashboards that cannot be migrated automatically #### 4. `conversion_data_loss_error` - Data loss detected during conversion - Automatically checks that panels, queries, annotations, and links are preserved - Triggered when target has fewer items than source - Includes detailed loss metrics in logs (see [Data Loss Detection](#data-loss-detection)) ### Logging #### Log structure All migration logs use structured logging with consistent field names: **Base Fields (always present):** - `sourceVersionAPI` - Source API version - `targetVersionAPI` - Target API version - `dashboardUID` - Unique identifier of the dashboard being converted **Schema Version Fields (v0/v1 dashboards only):** - `sourceSchemaVersion` - Source schema version number - `targetSchemaVersion` - Target schema version number - `erroredSchemaVersionFunc` - Name of the schema migration function that failed (on error) **Error Fields (failures only):** - `errorType` - Same classification as metrics error_type label - `erroredConversionFunc` - Name of the conversion function that failed - `error` - The actual error message **Data Loss Fields (conversion_data_loss_error only):** - `panelsLost` - Number of panels lost - `queriesLost` - Number of queries lost - `annotationsLost` - Number of annotations lost - `linksLost` - Number of links lost - `variablesLost` - Number of template variables lost #### Log levels ##### Success (DEBUG level) ```json { "level": "debug", "msg": "Dashboard conversion succeeded", "sourceVersionAPI": "dashboard.grafana.app/v0alpha1", "targetVersionAPI": "dashboard.grafana.app/v1beta1", "dashboardUID": "abc123", "sourceSchemaVersion": 16, "targetSchemaVersion": 41 } ``` ##### Conversion/Migration Error (ERROR level) ```json { "level": "error", "msg": "Dashboard conversion failed", "sourceVersionAPI": "dashboard.grafana.app/v0alpha1", "targetVersionAPI": "dashboard.grafana.app/v1beta1", "erroredConversionFunc": "Convert_V0_to_V1", "dashboardUID": "abc123", "sourceSchemaVersion": 16, "targetSchemaVersion": 41, "erroredSchemaVersionFunc": "V24", "errorType": "schema_version_migration_error", "error": "migration failed: table panel plugin not found" } ``` ##### Minimum Version Error (WARN level) ```json { "level": "warn", "msg": "Dashboard conversion failed", "sourceVersionAPI": "dashboard.grafana.app/v0alpha1", "targetVersionAPI": "dashboard.grafana.app/v1beta1", "erroredConversionFunc": "Convert_V0_to_V1", "dashboardUID": "def456", "sourceSchemaVersion": 10, "targetSchemaVersion": 41, "erroredSchemaVersionFunc": "", "errorType": "schema_minimum_version_error", "error": "dashboard schema version 10 cannot be migrated" } ``` ##### Data Loss Error (ERROR level) ```json { "level": "error", "msg": "Dashboard conversion failed", "sourceVersionAPI": "dashboard.grafana.app/v1beta1", "targetVersionAPI": "dashboard.grafana.app/v2alpha1", "erroredConversionFunc": "V1beta1_to_V2alpha1", "dashboardUID": "abc123", "sourceSchemaVersion": 42, "targetSchemaVersion": 42, "panelsLost": 0, "queriesLost": 2, "annotationsLost": 0, "linksLost": 0, "variablesLost": 0, "errorType": "conversion_data_loss_error", "error": "data loss detected: query count decreased from 7 to 5" } ``` ### Data Loss Detection **Automatic Runtime Checks:** Every conversion automatically detects data loss by comparing: - **Panel count** - Visualization panels (regular + library panels) - **Query count** - Data source queries (excludes invalid row panel queries) - **Annotation count** - Dashboard-level annotations - **Link count** - Navigation links - **Variable count** - Template variables (from `templating.list` in v0/v1, `variables` in v2) **Detection Logic:** - ✅ **Allows additions**: Default annotations, enriched data - ❌ **Detects losses**: Any decrease in counts triggers `conversion_data_loss_error` **Testing:** Run comprehensive data loss tests on all conversion test files: ```bash # Test all conversions for data loss go test ./apps/dashboard/pkg/migration/conversion/... -run TestDataLossDetectionOnAllInputFiles -v # Test shows detailed panel/query analysis when loss is detected ``` **Implementation:** See `conversion/conversion_data_loss_detection.go` and `conversion/README.md` for details. ### Implementation Details #### Automatic instrumentation All dashboard conversions are automatically instrumented via the `withConversionMetrics` wrapper function: ```go // All conversion functions are wrapped automatically // Includes metrics, logging, and data loss detection s.AddConversionFunc((*dashv0.Dashboard)(nil), (*dashv1.Dashboard)(nil), withConversionMetrics(dashv0.APIVERSION, dashv1.APIVERSION, func(a, b interface{}, scope conversion.Scope) error { return Convert_V0_to_V1(a.(*dashv0.Dashboard), b.(*dashv1.Dashboard), scope) })) ``` #### Error handling Custom error types provide structured error information: ```go // Schema migration errors type MigrationError struct { msg string targetVersion int currentVersion int functionName string } // API conversion errors type ConversionError struct { msg string functionName string currentAPIVersion string targetAPIVersion string } // Data loss errors are detected when dashboard components (panels, queries, annotations, links, variables) // are lost during conversion type ConversionDataLossError struct { functionName string // Function where data loss was detected (e.g., "V1_to_V2alpha1") message string // Detailed error message with loss statistics sourceAPIVersion string // Source API version (e.g., "dashboard.grafana.app/v1beta1") targetAPIVersion string // Target API version (e.g., "dashboard.grafana.app/v2alpha1") } ``` ### Registration #### Metrics registration Metrics must be registered with Prometheus during service initialization: ```go import "github.com/grafana/grafana/apps/dashboard/pkg/migration" // Register metrics with Prometheus migration.RegisterMetrics(prometheusRegistry) ``` #### Available metrics The following metrics are available after registration: ```go // Success counter migration.MDashboardConversionSuccessTotal // Failure counter migration.MDashboardConversionFailureTotal ``` ## Related Documentation - [Schema Migration Guide](./schemaversion/README.md) - Complete guide for creating new dashboard schema migrations - [V2 to V1 Layout Conversion](./conversion/v2_to_v1_layout_conversion.md) - How V2 layouts (GridLayout, RowsLayout, TabsLayout, AutoGridLayout) are converted to V1 panel arrays - [PR #110178 - Dashboard migration: Add missing metrics registration](https://github.com/grafana/grafana/pull/110178)