Files
grafana/apps/dashboard/pkg/migration/conversion/conversion_test.go
T
Dominik Prokop 20b08ccaab Add v2beta1 api version: Consolidate schema breaking changes (#108172)
* Revert "Revert: Future-proofing query and data source model in Dashboard Sche… (#107985)"

This reverts commit 13a89d4ae3.

* Revert "Revert "Schema V2: Simplify annotations v1<->v2 conversions" (#107984)"

This reverts commit 2b8c5bea1a.

* make gen apps

* e2e update

* Use v2alpha2 by default (#108177)

* Use v2alpha2 by default

* Apply only DS changes to alpha2

* Use v2alpha2 by default except to query

* Create a v2 index in @grafana/schema

* Update path and apply lint

* Update tests

* Update imports to v2 status

* Fix failing openapi test

* Schemav2 breaking changes: conversion implementation (#108224)

* provision v2alpha1 dashboard

* Run conversions for DS refactor

* Run snapshot testing on conversions

* Normalize output name

* Update snapshots to include all panel and variable cases

* fix lint

* fix lint

* fix test and go lint

* more go lint

---------

Co-authored-by: Ivan Ortega <ivanortegaalba@gmail.com>
Co-authored-by: Haris Rozajac <haris.rozajac12@gmail.com>

* Schema v2: Introduce group/datasource convention to GroupBy and AdHoc variable (#108237)

* Schema v2: Introduce group/datasource convention to GroupBy and AdHoc variables

* add conversion

* App Installer: Authorizer support (#108419)

* Chore: use `satisfies` and remove a load of `any`s (#108397)

use satisfies and remove a load of anys

* improve logging and fail unified-storage migration with more than 0 errors (#108471)

improve logging and fail unified-storage migration with more than 0 errors

* fix conversion test

* Secrets: Create more granular fixed roles for SecureValues (#108382)

* Provisioning: Fix bug in job progress recording (#108440)

Fix bug in job progress recording

* Provisioning: Fix ImportAllPanelsFromLocalRepository test (#108441)

* Provisioning: Skip flaky test

* Fix flaky provisioning test

* Fix lint

---------

Co-authored-by: Roberto Jimenez Sanchez <roberto.jimenez@grafana.com>

* BulkDeleteProvisionedResource: Move progress bar into a second step (#108417)

* Move progress bar into a second step

---------

Co-authored-by: Alex Khomenko <Clarity-89@users.noreply.github.com>

* [Dashboard Schema Codegen] Move dashboard CUE codegen block back up into kind body (#108476)

[Dashboard Schema Codegen] Move dashboard CUE codegen block back up into kind body to make sure new versions have the same settings.

---------

Co-authored-by: Haris Rozajac <haris.rozajac12@gmail.com>
Co-authored-by: Todd Treece <360020+toddtreece@users.noreply.github.com>
Co-authored-by: Ashley Harrison <ashley.harrison@grafana.com>
Co-authored-by: Will Assis <35489495+gassiss@users.noreply.github.com>
Co-authored-by: Matheus Macabu <macabu@users.noreply.github.com>
Co-authored-by: Roberto Jiménez Sánchez <jszroberto@gmail.com>
Co-authored-by: Roberto Jimenez Sanchez <roberto.jimenez@grafana.com>
Co-authored-by: Yunwen Zheng <yunwen.zheng@grafana.com>
Co-authored-by: Alex Khomenko <Clarity-89@users.noreply.github.com>
Co-authored-by: Austin Pond <IfSentient@users.noreply.github.com>
Co-authored-by: Ivan Ortega <ivanortegaalba@gmail.com>

* Dashboard Schema V2: Refactor VizConfigKind to follow DataQueryKind convention (#108148)

* Dashboards API: Register v2alpha2 API

* Prepare conversion functions

* Fix test

* Refactor VizConfigKind to follow DataQueryKind convention

* fix tests

* use new dataquerykind convention alpha 2

* add conversion

* fix tests

* fix tests

* fix another test

* Fix merge

---------

Co-authored-by: Dominik Prokop <dominik.prokop@grafana.com>

* fix k8s codegen

* Update e2e-playwright/dashboards/TestV2Dashboard.json

* Update e2e/dashboards/TestV2Dashboard.json

* revert app generation for non-related apps

* try again

* another try

* also revert folder and secret app generation

* v2alpha1 provisioned dashboard

* Fix kind

* Fix conversion snapshots

* Update API discovery registry

* Rename to v2beta1

* Rename migrations

* Update apps/dashboard/pkg/apis/dashboard/v2beta1/doc.go

Co-authored-by: Stephanie Hingtgen <stephanie.hingtgen@grafana.com>

* Ensure conditional rendering and other non changed properties

---------

Co-authored-by: Ivan Ortega <ivanortegaalba@gmail.com>
Co-authored-by: Haris Rozajac <haris.rozajac12@gmail.com>
Co-authored-by: Todd Treece <360020+toddtreece@users.noreply.github.com>
Co-authored-by: Ashley Harrison <ashley.harrison@grafana.com>
Co-authored-by: Will Assis <35489495+gassiss@users.noreply.github.com>
Co-authored-by: Matheus Macabu <macabu@users.noreply.github.com>
Co-authored-by: Roberto Jiménez Sánchez <jszroberto@gmail.com>
Co-authored-by: Roberto Jimenez Sanchez <roberto.jimenez@grafana.com>
Co-authored-by: Yunwen Zheng <yunwen.zheng@grafana.com>
Co-authored-by: Alex Khomenko <Clarity-89@users.noreply.github.com>
Co-authored-by: Austin Pond <IfSentient@users.noreply.github.com>
Co-authored-by: Haris Rozajac <58232930+harisrozajac@users.noreply.github.com>
Co-authored-by: Stephanie Hingtgen <stephanie.hingtgen@grafana.com>
2025-07-30 15:01:27 +02:00

223 lines
7.4 KiB
Go

package conversion
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"
"testing"
"github.com/stretchr/testify/require"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"github.com/grafana/grafana/apps/dashboard/pkg/apis"
dashv0 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v0alpha1"
dashv1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1"
dashv2alpha1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1"
dashv2beta1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2beta1"
"github.com/grafana/grafana/apps/dashboard/pkg/migration"
"github.com/grafana/grafana/apps/dashboard/pkg/migration/testutil"
common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
"github.com/grafana/grafana/pkg/apimachinery/utils"
)
func TestConversionMatrixExist(t *testing.T) {
// Initialize the migrator with a test data source provider
migration.Initialize(testutil.GetTestProvider())
versions := []v1.Object{
&dashv0.Dashboard{Spec: common.Unstructured{Object: map[string]any{"title": "dashboardV0"}}},
&dashv1.Dashboard{Spec: common.Unstructured{Object: map[string]any{"title": "dashboardV1"}}},
&dashv2alpha1.Dashboard{Spec: dashv2alpha1.DashboardSpec{Title: "dashboardV2alpha1"}},
&dashv2beta1.Dashboard{Spec: dashv2beta1.DashboardSpec{Title: "dashboardV2beta1"}},
}
scheme := runtime.NewScheme()
err := RegisterConversions(scheme)
require.NoError(t, err)
for idx, in := range versions {
kind := fmt.Sprintf("%T", in)[1:]
t.Run(kind, func(t *testing.T) {
for i, out := range versions {
if i == idx {
continue // skip the same version
}
err = scheme.Convert(in, out, nil)
require.NoError(t, err)
}
// Make sure we get the right title for each value
meta, err := utils.MetaAccessor(in)
require.NoError(t, err)
require.True(t, strings.HasPrefix(meta.FindTitle(""), "dashboard"))
})
}
}
func TestDeepCopyValid(t *testing.T) {
dash1 := &dashv0.Dashboard{}
meta1, err := utils.MetaAccessor(dash1)
require.NoError(t, err)
meta1.SetFolder("f1")
require.Equal(t, "f1", dash1.Annotations[utils.AnnoKeyFolder])
dash1Copy := dash1.DeepCopyObject()
metaCopy, err := utils.MetaAccessor(dash1Copy)
require.NoError(t, err)
require.Equal(t, "f1", metaCopy.GetFolder())
// Changing a property on the copy should not effect the original
metaCopy.SetFolder("XYZ")
require.Equal(t, "f1", meta1.GetFolder()) // 💣💣💣
}
func TestDashboardConversionToAllVersions(t *testing.T) {
// Initialize the migrator with a test data source provider
migration.Initialize(testutil.GetTestProvider())
// Set up conversion scheme
scheme := runtime.NewScheme()
err := RegisterConversions(scheme)
require.NoError(t, err)
// Read all files from input directory
files, err := os.ReadDir(filepath.Join("testdata", "input"))
require.NoError(t, err, "Failed to read input directory")
for _, file := range files {
if file.IsDir() {
continue
}
t.Run(fmt.Sprintf("Convert_%s", file.Name()), func(t *testing.T) {
// Read input dashboard file
inputFile := filepath.Join("testdata", "input", file.Name())
// ignore gosec G304 as this function is only used in the test process
//nolint:gosec
inputData, err := os.ReadFile(inputFile)
require.NoError(t, err, "Failed to read input file")
// Parse the input dashboard to get its version
var rawDash map[string]interface{}
err = json.Unmarshal(inputData, &rawDash)
require.NoError(t, err, "Failed to unmarshal dashboard JSON")
// Extract apiVersion
apiVersion, ok := rawDash["apiVersion"].(string)
require.True(t, ok, "apiVersion not found or not a string")
// Parse group and version from apiVersion (format: "group/version")
parts := strings.Split(apiVersion, "/")
require.Equal(t, 2, len(parts), "apiVersion should be in format 'group/version'")
sourceVersion := parts[1]
// Create source object based on version
var sourceDash v1.Object
switch sourceVersion {
case "v0alpha1":
var dash dashv0.Dashboard
err = json.Unmarshal(inputData, &dash)
sourceDash = &dash
case "v1beta1":
var dash dashv1.Dashboard
err = json.Unmarshal(inputData, &dash)
sourceDash = &dash
case "v2alpha1":
var dash dashv2alpha1.Dashboard
err = json.Unmarshal(inputData, &dash)
sourceDash = &dash
case "v2beta1":
var dash dashv2beta1.Dashboard
err = json.Unmarshal(inputData, &dash)
sourceDash = &dash
default:
t.Fatalf("Unsupported source version: %s", sourceVersion)
}
require.NoError(t, err, "Failed to unmarshal dashboard into typed object")
// Ensure output directory exists
outDir := filepath.Join("testdata", "output")
// ignore gosec G301 as this function is only used in the test process
//nolint:gosec
err = os.MkdirAll(outDir, 0755)
require.NoError(t, err, "Failed to create output directory")
// Get target versions from the dashboard manifest
manifest := apis.LocalManifest()
targetVersions := make(map[string]runtime.Object)
// Get original filename without extension
originalName := strings.TrimSuffix(file.Name(), ".json")
// Get all Dashboard versions from the manifest
for _, kind := range manifest.ManifestData.Kinds() {
if kind.Kind == "Dashboard" {
for _, version := range kind.Versions {
// Skip converting to the same version
if version.VersionName == sourceVersion {
continue
}
filename := fmt.Sprintf("%s.%s.json", originalName, version.VersionName)
// Create target object based on version
switch version.VersionName {
case "v0alpha1":
targetVersions[filename] = &dashv0.Dashboard{}
case "v1beta1":
targetVersions[filename] = &dashv1.Dashboard{}
case "v2alpha1":
targetVersions[filename] = &dashv2alpha1.Dashboard{}
case "v2beta1":
targetVersions[filename] = &dashv2beta1.Dashboard{}
default:
t.Logf("Unknown version %s, skipping", version.VersionName)
}
}
break
}
}
// Convert to each target version
for filename, target := range targetVersions {
t.Run(fmt.Sprintf("Convert_to_%s", filename), func(t *testing.T) {
// Create a copy of the input dashboard for conversion
inputCopy := sourceDash.(runtime.Object).DeepCopyObject()
// Convert to target version
err = scheme.Convert(inputCopy, target, nil)
require.NoError(t, err, "Conversion failed for %s", filename)
// Test the changes in the conversion result
testConversion(t, target.(v1.Object), filename, outDir)
})
}
})
}
}
func testConversion(t *testing.T, convertedDash v1.Object, filename, outputDir string) {
t.Helper()
outPath := filepath.Join(outputDir, filename)
outBytes, err := json.MarshalIndent(convertedDash, "", " ")
require.NoError(t, err, "failed to marshal converted dashboard")
if _, err := os.Stat(outPath); os.IsNotExist(err) {
err = os.WriteFile(outPath, outBytes, 0644)
require.NoError(t, err, "failed to write new output file %s", outPath)
t.Logf("✓ Created new output file: %s", filename)
return
}
// ignore gosec G304 as this function is only used in the test process
//nolint:gosec
existingBytes, err := os.ReadFile(outPath)
require.NoError(t, err, "failed to read existing output file")
require.JSONEq(t, string(existingBytes), string(outBytes), "%s did not match", outPath)
t.Logf("✓ Conversion to %s matches existing file", filename)
}