From be79eb0acea63ebbe3a52e01a0de8fde0e31136b Mon Sep 17 00:00:00 2001 From: Bruno Abrantes Date: Mon, 21 Jul 2025 15:32:24 +0200 Subject: [PATCH] fix: handle dashboards without version entries in unified storage migration (#108139) * fix: handle dashboards without version entries in migration Signed-off-by: Bruno Abrantes * fix: adds AllowFallback flag to enable COALESCE logic explicitly during migrations Signed-off-by: Bruno Abrantes * fix: update dependencies Signed-off-by: Bruno Abrantes * fix: maintain original behavior when not using AllowFallback option Signed-off-by: Bruno Abrantes --------- Signed-off-by: Bruno Abrantes --- go.mod | 2 +- go.sum | 4 +- pkg/registry/apis/dashboard/legacy/migrate.go | 9 +- .../apis/dashboard/legacy/migrate_test.go | 154 ++++++++++++++++++ .../apis/dashboard/legacy/queries_test.go | 9 + .../dashboard/legacy/query_dashboards.sql | 25 ++- .../apis/dashboard/legacy/sql_dashboards.go | 1 + .../dashboard/legacy/sql_dashboards_test.go | 59 +++++++ ...--query_dashboards-export_with_history.sql | 4 +- ...uery_dashboards-history_uid_at_version.sql | 18 +- ...ery_dashboards-migration_with_fallback.sql | 32 ++++ ...--query_dashboards-export_with_history.sql | 4 +- ...uery_dashboards-history_uid_at_version.sql | 18 +- ...ery_dashboards-migration_with_fallback.sql | 32 ++++ ...--query_dashboards-export_with_history.sql | 4 +- ...uery_dashboards-history_uid_at_version.sql | 18 +- ...ery_dashboards-migration_with_fallback.sql | 32 ++++ pkg/registry/apis/dashboard/legacy/types.go | 4 + 18 files changed, 386 insertions(+), 43 deletions(-) create mode 100644 pkg/registry/apis/dashboard/legacy/migrate_test.go create mode 100755 pkg/registry/apis/dashboard/legacy/testdata/mysql--query_dashboards-migration_with_fallback.sql create mode 100755 pkg/registry/apis/dashboard/legacy/testdata/postgres--query_dashboards-migration_with_fallback.sql create mode 100755 pkg/registry/apis/dashboard/legacy/testdata/sqlite--query_dashboards-migration_with_fallback.sql diff --git a/go.mod b/go.mod index b3a1af0ab63..6bd05593cb1 100644 --- a/go.mod +++ b/go.mod @@ -230,7 +230,7 @@ require ( require ( github.com/grafana/grafana/apps/advisor v0.0.0-20250627191313-2f1a6ae1712b // @grafana/plugins-platform-backend github.com/grafana/grafana/apps/alerting/notifications v0.0.0-20250627191313-2f1a6ae1712b // @grafana/alerting-backend - github.com/grafana/grafana/apps/dashboard v0.0.0-20250716154214-974103c6fa20 // @grafana/grafana-app-platform-squad @grafana/dashboards-squad + github.com/grafana/grafana/apps/dashboard v0.0.0-20250716132114-6fd75ebc5441 // @grafana/grafana-app-platform-squad @grafana/dashboards-squad github.com/grafana/grafana/apps/folder v0.0.0-20250627191313-2f1a6ae1712b // @grafana/grafana-search-and-storage github.com/grafana/grafana/apps/iam v0.0.0-20250627191313-2f1a6ae1712b // @grafana/identity-access-team github.com/grafana/grafana/apps/investigations v0.0.0-20250627191313-2f1a6ae1712b // @fcjack @matryer diff --git a/go.sum b/go.sum index 292d12d19c4..1c3e117e23b 100644 --- a/go.sum +++ b/go.sum @@ -1613,8 +1613,8 @@ github.com/grafana/grafana/apps/advisor v0.0.0-20250627191313-2f1a6ae1712b h1:8o github.com/grafana/grafana/apps/advisor v0.0.0-20250627191313-2f1a6ae1712b/go.mod h1:q+h3HbmqU/PposW6lq8cMle1v8vuyX1LCMrGzbabHxc= github.com/grafana/grafana/apps/alerting/notifications v0.0.0-20250627191313-2f1a6ae1712b h1:jr+C3epmjhd5Yyob4P1Z/dPaW4LRTkU5UJLXsI4eaeM= github.com/grafana/grafana/apps/alerting/notifications v0.0.0-20250627191313-2f1a6ae1712b/go.mod h1:WpI7TCck4P2wKTO2WJLBRcfOWvUGvTdxYu3QqS3z7jM= -github.com/grafana/grafana/apps/dashboard v0.0.0-20250716154214-974103c6fa20 h1:2aeZOXOdJID5BbrUtBWG+yGUU4wDXkqwsq9drSalrDE= -github.com/grafana/grafana/apps/dashboard v0.0.0-20250716154214-974103c6fa20/go.mod h1:1XWiRSVuDQiayapHhQiDc4S4e9GzEZgg/3GeNCuDgn4= +github.com/grafana/grafana/apps/dashboard v0.0.0-20250716132114-6fd75ebc5441 h1:+TSbaxCXBZrKkdROWBzdWna8uStE1f9LYd7GiqjVfz8= +github.com/grafana/grafana/apps/dashboard v0.0.0-20250716132114-6fd75ebc5441/go.mod h1:1XWiRSVuDQiayapHhQiDc4S4e9GzEZgg/3GeNCuDgn4= github.com/grafana/grafana/apps/folder v0.0.0-20250627191313-2f1a6ae1712b h1:31MwoIKKT9Ay0ZjbT4lkfcPijiWogUWzXs2EjrCgodI= github.com/grafana/grafana/apps/folder v0.0.0-20250627191313-2f1a6ae1712b/go.mod h1:dLtYBp1pza5HYalezNvzlP8JDeKrZ5BKTonDgEOE0NY= github.com/grafana/grafana/apps/iam v0.0.0-20250627191313-2f1a6ae1712b h1:NV8v9xdM/pzjjy+1cLqUseia3bYcvQGh88vZdMW/jA0= diff --git a/pkg/registry/apis/dashboard/legacy/migrate.go b/pkg/registry/apis/dashboard/legacy/migrate.go index 5692b490e95..aa534d9643a 100644 --- a/pkg/registry/apis/dashboard/legacy/migrate.go +++ b/pkg/registry/apis/dashboard/legacy/migrate.go @@ -211,10 +211,11 @@ func (a *dashboardSqlAccess) countValues(ctx context.Context, opts MigrateOption func (a *dashboardSqlAccess) migrateDashboards(ctx context.Context, orgId int64, opts MigrateOptions, stream resourcepb.BulkStore_BulkProcessClient) (*BlobStoreInfo, error) { query := &DashboardQuery{ - OrgID: orgId, - Limit: 100000000, - GetHistory: opts.WithHistory, // include history - Order: "ASC", // oldest first + OrgID: orgId, + Limit: 100000000, + GetHistory: opts.WithHistory, // include history + AllowFallback: true, // allow fallback to dashboard table during migration + Order: "ASC", // oldest first } blobs := &BlobStoreInfo{} diff --git a/pkg/registry/apis/dashboard/legacy/migrate_test.go b/pkg/registry/apis/dashboard/legacy/migrate_test.go new file mode 100644 index 00000000000..5b134cb31dc --- /dev/null +++ b/pkg/registry/apis/dashboard/legacy/migrate_test.go @@ -0,0 +1,154 @@ +package legacy + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/grafana/grafana/pkg/storage/legacysql" + "github.com/grafana/grafana/pkg/storage/unified/sql/sqltemplate" + "github.com/grafana/grafana/pkg/storage/unified/sql/sqltemplate/mocks" +) + +func TestDashboardMigrationQuery(t *testing.T) { + // Test that migration queries use AllowFallback flag correctly + nodb := &legacysql.LegacyDatabaseHelper{ + Table: func(n string) string { + return "grafana." + n + }, + } + + t.Run("Migration query should enable AllowFallback flag", func(t *testing.T) { + // Create a migration query as would be used in actual migration + migrationQuery := &DashboardQuery{ + OrgID: 1, + GetHistory: true, // Migration includes history + AllowFallback: true, // This is the key flag for migration + Order: "ASC", // Migration uses ascending order + } + + // Verify UseHistoryTable returns true (requirement for COALESCE logic) + require.True(t, migrationQuery.UseHistoryTable(), "Migration query should use history table") + + // Verify the flag is set correctly + require.True(t, migrationQuery.AllowFallback, "Migration query should allow fallback") + require.True(t, migrationQuery.GetHistory, "Migration query should get history") + require.Equal(t, "ASC", migrationQuery.Order, "Migration should use ascending order") + }) + + t.Run("Regular history query should not use AllowFallback", func(t *testing.T) { + // Regular history query without migration + historyQuery := &DashboardQuery{ + OrgID: 1, + GetHistory: true, + Order: "DESC", + } + + require.True(t, historyQuery.UseHistoryTable(), "History query should use history table") + require.False(t, historyQuery.AllowFallback, "Regular history query should not allow fallback") + require.True(t, historyQuery.GetHistory, "History query should get history") + }) + + t.Run("Migration query template produces COALESCE SQL", func(t *testing.T) { + // Test that the SQL template produces COALESCE logic for migration queries + migrationQuery := &DashboardQuery{ + OrgID: 1, + GetHistory: true, + AllowFallback: true, + Order: "ASC", + } + + req := newQueryReq(nodb, migrationQuery) + req.SQLTemplate = mocks.NewTestingSQLTemplate() + + // Execute the template to get the generated SQL + rawQuery, err := sqltemplate.Execute(sqlQueryDashboards, &req) + require.NoError(t, err) + + sql := rawQuery + + // Verify that COALESCE functions are present in the generated SQL + // These should be used when GetHistory=true AND AllowFallback=true + require.Contains(t, sql, "COALESCE(dashboard_version.created, dashboard.updated)", + "Migration SQL should contain COALESCE for updated timestamp") + require.Contains(t, sql, "COALESCE(dashboard_version.version, dashboard.version)", + "Migration SQL should contain COALESCE for version") + require.Contains(t, sql, "COALESCE(dashboard_version.data, dashboard.data)", + "Migration SQL should contain COALESCE for data") + require.Contains(t, sql, "COALESCE(dashboard_version.api_version, dashboard.api_version)", + "Migration SQL should contain COALESCE for api_version") + require.Contains(t, sql, "COALESCE(dashboard_version.message, '')", + "Migration SQL should contain COALESCE for message with empty string fallback") + + // Verify ORDER BY uses COALESCE as well + require.Contains(t, sql, "COALESCE(dashboard_version.created, dashboard.updated) ASC", + "Migration SQL should ORDER BY COALESCED created timestamp") + require.Contains(t, sql, "COALESCE(dashboard_version.version, dashboard.version) ASC", + "Migration SQL should ORDER BY COALESCED version") + + // Verify it doesn't have the strict history table filter that would exclude NULL version entries + require.NotContains(t, sql, "dashboard_version.id IS NOT NULL", + "Migration SQL should not exclude dashboards without version entries") + }) + + t.Run("Regular history query produces strict SQL", func(t *testing.T) { + // Test that regular history queries still use strict dashboard_version fields + historyQuery := &DashboardQuery{ + OrgID: 1, + GetHistory: true, + Order: "DESC", + } + + req := newQueryReq(nodb, historyQuery) + req.SQLTemplate = mocks.NewTestingSQLTemplate() + + rawQuery, err := sqltemplate.Execute(sqlQueryDashboards, &req) + require.NoError(t, err) + + sql := rawQuery + + // Verify that direct dashboard_version fields are used (no COALESCE) + require.Contains(t, sql, "dashboard_version.created as updated", + "Regular history SQL should use direct dashboard_version.created") + require.Contains(t, sql, "dashboard_version.version", + "Regular history SQL should use direct dashboard_version.version") + require.Contains(t, sql, "dashboard_version.data", + "Regular history SQL should use direct dashboard_version.data") + + // NOTE: We intentionally do NOT add dashboard_version.id IS NOT NULL filter + // to allow for cases where dashboard_version entries might be missing + + // Should not contain COALESCE functions + require.NotContains(t, sql, "COALESCE(dashboard_version.created, dashboard.updated)", + "Regular history SQL should not contain COALESCE for updated") + }) +} + +func TestMigrateDashboardsConfiguration(t *testing.T) { + // Test the actual migration function configuration + + t.Run("Migration options should configure query correctly", func(t *testing.T) { + // Test the migration configuration as used in real migration + opts := MigrateOptions{ + WithHistory: true, // Migration includes history + } + + // This simulates what happens in migrateDashboards function + expectedQuery := &DashboardQuery{ + OrgID: 1, + Limit: 100000000, + GetHistory: opts.WithHistory, // Should be true + AllowFallback: true, // Should be true for migration + Order: "ASC", // Should be ASC for migration + } + + // Verify the configuration matches what migration sets up + require.True(t, expectedQuery.GetHistory, "Migration should enable GetHistory") + require.True(t, expectedQuery.AllowFallback, "Migration should enable AllowFallback") + require.Equal(t, "ASC", expectedQuery.Order, "Migration should use ascending order") + require.Equal(t, 100000000, expectedQuery.Limit, "Migration should use large limit") + + // Verify UseHistoryTable logic + require.True(t, expectedQuery.UseHistoryTable(), "Migration query should use history table") + }) +} diff --git a/pkg/registry/apis/dashboard/legacy/queries_test.go b/pkg/registry/apis/dashboard/legacy/queries_test.go index d2331267177..3ee82e899d7 100644 --- a/pkg/registry/apis/dashboard/legacy/queries_test.go +++ b/pkg/registry/apis/dashboard/legacy/queries_test.go @@ -85,6 +85,15 @@ func TestDashboardQueries(t *testing.T) { Order: "ASC", }), }, + { + Name: "migration_with_fallback", + Data: getQuery(&DashboardQuery{ + OrgID: 1, + GetHistory: true, + AllowFallback: true, + Order: "ASC", + }), + }, }, sqlQueryPanels: { { diff --git a/pkg/registry/apis/dashboard/legacy/query_dashboards.sql b/pkg/registry/apis/dashboard/legacy/query_dashboards.sql index d328be4e805..75439c60d03 100644 --- a/pkg/registry/apis/dashboard/legacy/query_dashboards.sql +++ b/pkg/registry/apis/dashboard/legacy/query_dashboards.sql @@ -13,14 +13,24 @@ SELECT created_user.uid as created_by, dashboard.created_by as created_by_id, {{ if .Query.UseHistoryTable }} - dashboard_version.created, + {{ if and .Query.GetHistory (not .Query.AllowFallback) }} + dashboard_version.created as updated, updated_user.uid as updated_by, - updated_user.id as created_by_id, + dashboard_version.created_by as updated_by_id, dashboard_version.version, dashboard_version.message, dashboard_version.data, dashboard_version.api_version {{ else }} + COALESCE(dashboard_version.created, dashboard.updated) as updated, + updated_user.uid as updated_by, + COALESCE(dashboard_version.created_by, dashboard.updated_by) as updated_by_id, + COALESCE(dashboard_version.version, dashboard.version) as version, + COALESCE(dashboard_version.message, '') as message, + COALESCE(dashboard_version.data, dashboard.data) as data, + COALESCE(dashboard_version.api_version, dashboard.api_version) as api_version + {{ end }} + {{ else }} dashboard.updated, updated_user.uid as updated_by, dashboard.updated_by as updated_by_id, @@ -35,7 +45,7 @@ LEFT OUTER JOIN {{ .Ident .VersionTable }} as dashboard_version ON dashboard.id {{ end }} LEFT OUTER JOIN {{ .Ident .ProvisioningTable }} as provisioning ON dashboard.id = provisioning.dashboard_id LEFT OUTER JOIN {{ .Ident .UserTable }} as created_user ON dashboard.created_by = created_user.id -LEFT OUTER JOIN {{ .Ident .UserTable }} as updated_user ON {{ if .Query.UseHistoryTable }}dashboard_version.created_by = updated_user.id{{ else }}dashboard.updated_by = updated_user.id{{ end }} +LEFT OUTER JOIN {{ .Ident .UserTable }} as updated_user ON {{ if .Query.UseHistoryTable }}{{ if and .Query.GetHistory (not .Query.AllowFallback) }}dashboard_version.created_by = updated_user.id{{ else }}COALESCE(dashboard_version.created_by, dashboard.updated_by) = updated_user.id{{ end }}{{ else }}dashboard.updated_by = updated_user.id{{ end }} WHERE dashboard.is_folder = {{ .Arg .Query.GetFolders }} AND dashboard.org_id = {{ .Arg .Query.OrgID }} {{ if .Query.UseHistoryTable }} @@ -45,11 +55,20 @@ WHERE dashboard.is_folder = {{ .Arg .Query.GetFolders }} {{ if .Query.Version }} AND dashboard_version.version = {{ .Arg .Query.Version }} {{ else if .Query.LastID }} + {{ if and .Query.GetHistory (not .Query.AllowFallback) }} AND dashboard_version.version < {{ .Arg .Query.LastID }} + {{ else }} + AND COALESCE(dashboard_version.version, dashboard.version) < {{ .Arg .Query.LastID }} + {{ end }} {{ end }} ORDER BY + {{ if and .Query.GetHistory (not .Query.AllowFallback) }} dashboard_version.created {{ .Query.Order }}, dashboard_version.version {{ .Query.Order }}, + {{ else }} + COALESCE(dashboard_version.created, dashboard.updated) {{ .Query.Order }}, + COALESCE(dashboard_version.version, dashboard.version) {{ .Query.Order }}, + {{ end }} dashboard.uid ASC {{ else }} {{ if .Query.UID }} diff --git a/pkg/registry/apis/dashboard/legacy/sql_dashboards.go b/pkg/registry/apis/dashboard/legacy/sql_dashboards.go index 1c14928daeb..5c5a8ff1d83 100644 --- a/pkg/registry/apis/dashboard/legacy/sql_dashboards.go +++ b/pkg/registry/apis/dashboard/legacy/sql_dashboards.go @@ -263,6 +263,7 @@ func (a *dashboardSqlAccess) scanRow(rows *sql.Rows, history bool) (*dashboardRo &updated, &updatedBy, &updatedByID, &version, &message, &data, &apiVersion, ) + switch apiVersion.String { case "": apiVersion.String = dashboardV0.VERSION // default value diff --git a/pkg/registry/apis/dashboard/legacy/sql_dashboards_test.go b/pkg/registry/apis/dashboard/legacy/sql_dashboards_test.go index 626d022d39d..41da615b6ff 100644 --- a/pkg/registry/apis/dashboard/legacy/sql_dashboards_test.go +++ b/pkg/registry/apis/dashboard/legacy/sql_dashboards_test.go @@ -128,6 +128,65 @@ func TestScanRow(t *testing.T) { require.Equal(t, "slo", manager.Identity) // the ID of the plugin require.Equal(t, "", meta.GetAnnotations()[utils.AnnoKeySourceChecksum]) // hash is not used on plugins }) + + t.Run("Migration scenario should use COALESCE logic with AllowFallback=true", func(t *testing.T) { + // This specifically tests the migration use case where GetHistory=true but AllowFallback=true + // allows the query to use COALESCE logic to fall back to dashboard table data when + // dashboard_version entries are missing. + + migrationTimestamp := timestamp.Add(2 * time.Hour) // Migration scenario timestamp + migrationVersion := int64(5) + migrationUpdatedUser := "migration_updater" + migrationMessage := "" // Empty message for migration (COALESCE behavior) + migrationData := []byte(`{"migration": "data", "title": "Migrated Dashboard"}`) + migrationAPIVersion := "v0alpha1" + + // In migration scenario, COALESCE functions return dashboard table values + // when dashboard_version values are NULL, ensuring all dashboards are migrated + rows := sqlmock.NewRows(columns).AddRow( + 1, id, title, folderUID, nil, "", // basic dashboard fields + "", "", "", 0, // origin fields + timestamp, createdUser, 0, // created fields + // These represent COALESCED values from dashboard table (not version table) + migrationTimestamp, migrationUpdatedUser, 0, migrationVersion, migrationMessage, migrationData, migrationAPIVersion, + ) + + mock.ExpectQuery("SELECT *").WillReturnRows(rows) + resultRows, err := mockDB.Query("SELECT *") + require.NoError(t, err) + defer resultRows.Close() // nolint:errcheck + resultRows.Next() + + // Test with history=true (migration scenario) - should work with COALESCED values + row, err := store.scanRow(resultRows, true) + require.NoError(t, err) + require.NotNil(t, row) + + // Verify migration scenario works correctly with fallback data + require.Equal(t, title, row.Dash.Name) + require.Equal(t, migrationVersion, row.RV) // Should use COALESCEd dashboard table version + require.Equal(t, common.Unstructured{ + Object: map[string]interface{}{ + "migration": "data", + "title": "Migrated Dashboard", + }, + }, row.Dash.Spec) // Should use COALESCEd dashboard table data + require.Equal(t, "default", row.Dash.Namespace) + + // Token should use the version in history mode (migration scenario) + require.Equal(t, &continueToken{orgId: int64(1), id: migrationVersion}, row.token) + + meta, err := utils.MetaAccessor(row.Dash) + require.NoError(t, err) + require.Equal(t, id, meta.GetDeprecatedInternalID()) // nolint:staticcheck + require.Equal(t, migrationVersion, meta.GetGeneration()) // Should use COALESCEd version + require.Equal(t, k8sTimestamp, meta.GetCreationTimestamp()) // Created timestamp unchanged + require.Equal(t, "user:"+createdUser, meta.GetCreatedBy()) // Original creator preserved + require.Equal(t, "user:"+migrationUpdatedUser, meta.GetUpdatedBy()) // COALESCEd updater + require.Equal(t, migrationMessage, meta.GetMessage()) // Empty message from COALESCE + require.Equal(t, folderUID, meta.GetFolder()) + require.Equal(t, "dashboard.grafana.app/"+migrationAPIVersion, row.Dash.APIVersion) + }) } func TestBuildSaveDashboardCommand(t *testing.T) { diff --git a/pkg/registry/apis/dashboard/legacy/testdata/mysql--query_dashboards-export_with_history.sql b/pkg/registry/apis/dashboard/legacy/testdata/mysql--query_dashboards-export_with_history.sql index e425f56c9c7..fd54e6dc949 100755 --- a/pkg/registry/apis/dashboard/legacy/testdata/mysql--query_dashboards-export_with_history.sql +++ b/pkg/registry/apis/dashboard/legacy/testdata/mysql--query_dashboards-export_with_history.sql @@ -12,9 +12,9 @@ SELECT dashboard.created, created_user.uid as created_by, dashboard.created_by as created_by_id, - dashboard_version.created, + dashboard_version.created as updated, updated_user.uid as updated_by, - updated_user.id as created_by_id, + dashboard_version.created_by as updated_by_id, dashboard_version.version, dashboard_version.message, dashboard_version.data, diff --git a/pkg/registry/apis/dashboard/legacy/testdata/mysql--query_dashboards-history_uid_at_version.sql b/pkg/registry/apis/dashboard/legacy/testdata/mysql--query_dashboards-history_uid_at_version.sql index e0c9df4fae1..e04b1ff7430 100755 --- a/pkg/registry/apis/dashboard/legacy/testdata/mysql--query_dashboards-history_uid_at_version.sql +++ b/pkg/registry/apis/dashboard/legacy/testdata/mysql--query_dashboards-history_uid_at_version.sql @@ -12,23 +12,23 @@ SELECT dashboard.created, created_user.uid as created_by, dashboard.created_by as created_by_id, - dashboard_version.created, + COALESCE(dashboard_version.created, dashboard.updated) as updated, updated_user.uid as updated_by, - updated_user.id as created_by_id, - dashboard_version.version, - dashboard_version.message, - dashboard_version.data, - dashboard_version.api_version + COALESCE(dashboard_version.created_by, dashboard.updated_by) as updated_by_id, + COALESCE(dashboard_version.version, dashboard.version) as version, + COALESCE(dashboard_version.message, '') as message, + COALESCE(dashboard_version.data, dashboard.data) as data, + COALESCE(dashboard_version.api_version, dashboard.api_version) as api_version FROM `grafana`.`dashboard` as dashboard LEFT OUTER JOIN `grafana`.`dashboard_version` as dashboard_version ON dashboard.id = dashboard_version.dashboard_id LEFT OUTER JOIN `grafana`.`dashboard_provisioning` as provisioning ON dashboard.id = provisioning.dashboard_id LEFT OUTER JOIN `grafana`.`user` as created_user ON dashboard.created_by = created_user.id -LEFT OUTER JOIN `grafana`.`user` as updated_user ON dashboard_version.created_by = updated_user.id +LEFT OUTER JOIN `grafana`.`user` as updated_user ON COALESCE(dashboard_version.created_by, dashboard.updated_by) = updated_user.id WHERE dashboard.is_folder = FALSE AND dashboard.org_id = 2 AND dashboard.uid = 'UUU' AND dashboard_version.version = 3 ORDER BY - dashboard_version.created DESC, - dashboard_version.version DESC, + COALESCE(dashboard_version.created, dashboard.updated) DESC, + COALESCE(dashboard_version.version, dashboard.version) DESC, dashboard.uid ASC diff --git a/pkg/registry/apis/dashboard/legacy/testdata/mysql--query_dashboards-migration_with_fallback.sql b/pkg/registry/apis/dashboard/legacy/testdata/mysql--query_dashboards-migration_with_fallback.sql new file mode 100755 index 00000000000..ba38c048136 --- /dev/null +++ b/pkg/registry/apis/dashboard/legacy/testdata/mysql--query_dashboards-migration_with_fallback.sql @@ -0,0 +1,32 @@ +SELECT + dashboard.org_id, + dashboard.id, + dashboard.uid, + dashboard.folder_uid, + dashboard.deleted, + plugin_id, + provisioning.name as repo_name, + provisioning.external_id as repo_path, + provisioning.check_sum as repo_hash, + provisioning.updated as repo_ts, + dashboard.created, + created_user.uid as created_by, + dashboard.created_by as created_by_id, + COALESCE(dashboard_version.created, dashboard.updated) as updated, + updated_user.uid as updated_by, + COALESCE(dashboard_version.created_by, dashboard.updated_by) as updated_by_id, + COALESCE(dashboard_version.version, dashboard.version) as version, + COALESCE(dashboard_version.message, '') as message, + COALESCE(dashboard_version.data, dashboard.data) as data, + COALESCE(dashboard_version.api_version, dashboard.api_version) as api_version +FROM `grafana`.`dashboard` as dashboard +LEFT OUTER JOIN `grafana`.`dashboard_version` as dashboard_version ON dashboard.id = dashboard_version.dashboard_id +LEFT OUTER JOIN `grafana`.`dashboard_provisioning` as provisioning ON dashboard.id = provisioning.dashboard_id +LEFT OUTER JOIN `grafana`.`user` as created_user ON dashboard.created_by = created_user.id +LEFT OUTER JOIN `grafana`.`user` as updated_user ON COALESCE(dashboard_version.created_by, dashboard.updated_by) = updated_user.id +WHERE dashboard.is_folder = FALSE + AND dashboard.org_id = 1 + ORDER BY + COALESCE(dashboard_version.created, dashboard.updated) ASC, + COALESCE(dashboard_version.version, dashboard.version) ASC, + dashboard.uid ASC diff --git a/pkg/registry/apis/dashboard/legacy/testdata/postgres--query_dashboards-export_with_history.sql b/pkg/registry/apis/dashboard/legacy/testdata/postgres--query_dashboards-export_with_history.sql index 16ddeda410f..4d0affb337f 100755 --- a/pkg/registry/apis/dashboard/legacy/testdata/postgres--query_dashboards-export_with_history.sql +++ b/pkg/registry/apis/dashboard/legacy/testdata/postgres--query_dashboards-export_with_history.sql @@ -12,9 +12,9 @@ SELECT dashboard.created, created_user.uid as created_by, dashboard.created_by as created_by_id, - dashboard_version.created, + dashboard_version.created as updated, updated_user.uid as updated_by, - updated_user.id as created_by_id, + dashboard_version.created_by as updated_by_id, dashboard_version.version, dashboard_version.message, dashboard_version.data, diff --git a/pkg/registry/apis/dashboard/legacy/testdata/postgres--query_dashboards-history_uid_at_version.sql b/pkg/registry/apis/dashboard/legacy/testdata/postgres--query_dashboards-history_uid_at_version.sql index 07702d215a4..61311c5c99b 100755 --- a/pkg/registry/apis/dashboard/legacy/testdata/postgres--query_dashboards-history_uid_at_version.sql +++ b/pkg/registry/apis/dashboard/legacy/testdata/postgres--query_dashboards-history_uid_at_version.sql @@ -12,23 +12,23 @@ SELECT dashboard.created, created_user.uid as created_by, dashboard.created_by as created_by_id, - dashboard_version.created, + COALESCE(dashboard_version.created, dashboard.updated) as updated, updated_user.uid as updated_by, - updated_user.id as created_by_id, - dashboard_version.version, - dashboard_version.message, - dashboard_version.data, - dashboard_version.api_version + COALESCE(dashboard_version.created_by, dashboard.updated_by) as updated_by_id, + COALESCE(dashboard_version.version, dashboard.version) as version, + COALESCE(dashboard_version.message, '') as message, + COALESCE(dashboard_version.data, dashboard.data) as data, + COALESCE(dashboard_version.api_version, dashboard.api_version) as api_version FROM "grafana"."dashboard" as dashboard LEFT OUTER JOIN "grafana"."dashboard_version" as dashboard_version ON dashboard.id = dashboard_version.dashboard_id LEFT OUTER JOIN "grafana"."dashboard_provisioning" as provisioning ON dashboard.id = provisioning.dashboard_id LEFT OUTER JOIN "grafana"."user" as created_user ON dashboard.created_by = created_user.id -LEFT OUTER JOIN "grafana"."user" as updated_user ON dashboard_version.created_by = updated_user.id +LEFT OUTER JOIN "grafana"."user" as updated_user ON COALESCE(dashboard_version.created_by, dashboard.updated_by) = updated_user.id WHERE dashboard.is_folder = FALSE AND dashboard.org_id = 2 AND dashboard.uid = 'UUU' AND dashboard_version.version = 3 ORDER BY - dashboard_version.created DESC, - dashboard_version.version DESC, + COALESCE(dashboard_version.created, dashboard.updated) DESC, + COALESCE(dashboard_version.version, dashboard.version) DESC, dashboard.uid ASC diff --git a/pkg/registry/apis/dashboard/legacy/testdata/postgres--query_dashboards-migration_with_fallback.sql b/pkg/registry/apis/dashboard/legacy/testdata/postgres--query_dashboards-migration_with_fallback.sql new file mode 100755 index 00000000000..364fbebf417 --- /dev/null +++ b/pkg/registry/apis/dashboard/legacy/testdata/postgres--query_dashboards-migration_with_fallback.sql @@ -0,0 +1,32 @@ +SELECT + dashboard.org_id, + dashboard.id, + dashboard.uid, + dashboard.folder_uid, + dashboard.deleted, + plugin_id, + provisioning.name as repo_name, + provisioning.external_id as repo_path, + provisioning.check_sum as repo_hash, + provisioning.updated as repo_ts, + dashboard.created, + created_user.uid as created_by, + dashboard.created_by as created_by_id, + COALESCE(dashboard_version.created, dashboard.updated) as updated, + updated_user.uid as updated_by, + COALESCE(dashboard_version.created_by, dashboard.updated_by) as updated_by_id, + COALESCE(dashboard_version.version, dashboard.version) as version, + COALESCE(dashboard_version.message, '') as message, + COALESCE(dashboard_version.data, dashboard.data) as data, + COALESCE(dashboard_version.api_version, dashboard.api_version) as api_version +FROM "grafana"."dashboard" as dashboard +LEFT OUTER JOIN "grafana"."dashboard_version" as dashboard_version ON dashboard.id = dashboard_version.dashboard_id +LEFT OUTER JOIN "grafana"."dashboard_provisioning" as provisioning ON dashboard.id = provisioning.dashboard_id +LEFT OUTER JOIN "grafana"."user" as created_user ON dashboard.created_by = created_user.id +LEFT OUTER JOIN "grafana"."user" as updated_user ON COALESCE(dashboard_version.created_by, dashboard.updated_by) = updated_user.id +WHERE dashboard.is_folder = FALSE + AND dashboard.org_id = 1 + ORDER BY + COALESCE(dashboard_version.created, dashboard.updated) ASC, + COALESCE(dashboard_version.version, dashboard.version) ASC, + dashboard.uid ASC diff --git a/pkg/registry/apis/dashboard/legacy/testdata/sqlite--query_dashboards-export_with_history.sql b/pkg/registry/apis/dashboard/legacy/testdata/sqlite--query_dashboards-export_with_history.sql index 16ddeda410f..4d0affb337f 100755 --- a/pkg/registry/apis/dashboard/legacy/testdata/sqlite--query_dashboards-export_with_history.sql +++ b/pkg/registry/apis/dashboard/legacy/testdata/sqlite--query_dashboards-export_with_history.sql @@ -12,9 +12,9 @@ SELECT dashboard.created, created_user.uid as created_by, dashboard.created_by as created_by_id, - dashboard_version.created, + dashboard_version.created as updated, updated_user.uid as updated_by, - updated_user.id as created_by_id, + dashboard_version.created_by as updated_by_id, dashboard_version.version, dashboard_version.message, dashboard_version.data, diff --git a/pkg/registry/apis/dashboard/legacy/testdata/sqlite--query_dashboards-history_uid_at_version.sql b/pkg/registry/apis/dashboard/legacy/testdata/sqlite--query_dashboards-history_uid_at_version.sql index 07702d215a4..61311c5c99b 100755 --- a/pkg/registry/apis/dashboard/legacy/testdata/sqlite--query_dashboards-history_uid_at_version.sql +++ b/pkg/registry/apis/dashboard/legacy/testdata/sqlite--query_dashboards-history_uid_at_version.sql @@ -12,23 +12,23 @@ SELECT dashboard.created, created_user.uid as created_by, dashboard.created_by as created_by_id, - dashboard_version.created, + COALESCE(dashboard_version.created, dashboard.updated) as updated, updated_user.uid as updated_by, - updated_user.id as created_by_id, - dashboard_version.version, - dashboard_version.message, - dashboard_version.data, - dashboard_version.api_version + COALESCE(dashboard_version.created_by, dashboard.updated_by) as updated_by_id, + COALESCE(dashboard_version.version, dashboard.version) as version, + COALESCE(dashboard_version.message, '') as message, + COALESCE(dashboard_version.data, dashboard.data) as data, + COALESCE(dashboard_version.api_version, dashboard.api_version) as api_version FROM "grafana"."dashboard" as dashboard LEFT OUTER JOIN "grafana"."dashboard_version" as dashboard_version ON dashboard.id = dashboard_version.dashboard_id LEFT OUTER JOIN "grafana"."dashboard_provisioning" as provisioning ON dashboard.id = provisioning.dashboard_id LEFT OUTER JOIN "grafana"."user" as created_user ON dashboard.created_by = created_user.id -LEFT OUTER JOIN "grafana"."user" as updated_user ON dashboard_version.created_by = updated_user.id +LEFT OUTER JOIN "grafana"."user" as updated_user ON COALESCE(dashboard_version.created_by, dashboard.updated_by) = updated_user.id WHERE dashboard.is_folder = FALSE AND dashboard.org_id = 2 AND dashboard.uid = 'UUU' AND dashboard_version.version = 3 ORDER BY - dashboard_version.created DESC, - dashboard_version.version DESC, + COALESCE(dashboard_version.created, dashboard.updated) DESC, + COALESCE(dashboard_version.version, dashboard.version) DESC, dashboard.uid ASC diff --git a/pkg/registry/apis/dashboard/legacy/testdata/sqlite--query_dashboards-migration_with_fallback.sql b/pkg/registry/apis/dashboard/legacy/testdata/sqlite--query_dashboards-migration_with_fallback.sql new file mode 100755 index 00000000000..364fbebf417 --- /dev/null +++ b/pkg/registry/apis/dashboard/legacy/testdata/sqlite--query_dashboards-migration_with_fallback.sql @@ -0,0 +1,32 @@ +SELECT + dashboard.org_id, + dashboard.id, + dashboard.uid, + dashboard.folder_uid, + dashboard.deleted, + plugin_id, + provisioning.name as repo_name, + provisioning.external_id as repo_path, + provisioning.check_sum as repo_hash, + provisioning.updated as repo_ts, + dashboard.created, + created_user.uid as created_by, + dashboard.created_by as created_by_id, + COALESCE(dashboard_version.created, dashboard.updated) as updated, + updated_user.uid as updated_by, + COALESCE(dashboard_version.created_by, dashboard.updated_by) as updated_by_id, + COALESCE(dashboard_version.version, dashboard.version) as version, + COALESCE(dashboard_version.message, '') as message, + COALESCE(dashboard_version.data, dashboard.data) as data, + COALESCE(dashboard_version.api_version, dashboard.api_version) as api_version +FROM "grafana"."dashboard" as dashboard +LEFT OUTER JOIN "grafana"."dashboard_version" as dashboard_version ON dashboard.id = dashboard_version.dashboard_id +LEFT OUTER JOIN "grafana"."dashboard_provisioning" as provisioning ON dashboard.id = provisioning.dashboard_id +LEFT OUTER JOIN "grafana"."user" as created_user ON dashboard.created_by = created_user.id +LEFT OUTER JOIN "grafana"."user" as updated_user ON COALESCE(dashboard_version.created_by, dashboard.updated_by) = updated_user.id +WHERE dashboard.is_folder = FALSE + AND dashboard.org_id = 1 + ORDER BY + COALESCE(dashboard_version.created, dashboard.updated) ASC, + COALESCE(dashboard_version.version, dashboard.version) ASC, + dashboard.uid ASC diff --git a/pkg/registry/apis/dashboard/legacy/types.go b/pkg/registry/apis/dashboard/legacy/types.go index f20f9232afb..4ffa7b80863 100644 --- a/pkg/registry/apis/dashboard/legacy/types.go +++ b/pkg/registry/apis/dashboard/legacy/types.go @@ -27,6 +27,10 @@ type DashboardQuery struct { GetHistory bool Version int64 + // Allow fallback to dashboard table when version data is missing + // Used during migration to handle dashboards without version entries + AllowFallback bool + // Only folders GetFolders bool