fix: handle dashboards without version entries in unified storage migration (#108139)

* fix: handle dashboards without version entries in migration

Signed-off-by: Bruno Abrantes <bruno@brunoabrantes.com>

* fix: adds AllowFallback flag to enable COALESCE logic explicitly during migrations

Signed-off-by: Bruno Abrantes <bruno@brunoabrantes.com>

* fix: update dependencies

Signed-off-by: Bruno Abrantes <bruno@brunoabrantes.com>

* fix: maintain original behavior when not using AllowFallback option

Signed-off-by: Bruno Abrantes <bruno@brunoabrantes.com>

---------

Signed-off-by: Bruno Abrantes <bruno@brunoabrantes.com>
This commit is contained in:
Bruno Abrantes
2025-07-21 15:32:24 +02:00
committed by GitHub
parent 73c09efd03
commit be79eb0ace
18 changed files with 386 additions and 43 deletions
+1 -1
View File
@@ -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
+2 -2
View File
@@ -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=
@@ -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{}
@@ -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")
})
}
@@ -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: {
{
@@ -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 }}
@@ -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
@@ -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) {
@@ -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,
@@ -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
@@ -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
@@ -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,
@@ -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
@@ -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
@@ -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,
@@ -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
@@ -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
@@ -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