Compare commits

..

1 Commits

Author SHA1 Message Date
Kristina Demeshchik f1b63e4d29 add migration loggers when json drops by more thatn 20% 2026-01-06 16:19:22 -05:00
10 changed files with 65 additions and 6 deletions
@@ -1,6 +1,7 @@
package conversion
import (
"encoding/json"
"errors"
"fmt"
@@ -82,6 +83,17 @@ type dashboardStats struct {
annotationCount int
linkCount int
variableCount int
jsonSize int
}
// getJSONSize returns the size of an object when serialized to JSON
// This is a generic way to detect content loss without knowing the specific structure
func getJSONSize(v interface{}) int {
data, err := json.Marshal(v)
if err != nil {
return 0
}
return len(data)
}
// countPanelsV0V1 counts panels in v0alpha1 or v1beta1 dashboard spec (unstructured JSON)
@@ -238,6 +250,7 @@ func collectStatsV0V1(spec map[string]interface{}) dashboardStats {
annotationCount: countAnnotationsV0V1(spec),
linkCount: countLinksV0V1(spec),
variableCount: countVariablesV0V1(spec),
jsonSize: getJSONSize(spec),
}
}
@@ -289,6 +302,7 @@ func collectStatsV2alpha1(spec dashv2alpha1.DashboardSpec) dashboardStats {
annotationCount: countAnnotationsV2(spec.Annotations),
linkCount: countLinksV2(spec.Links),
variableCount: countVariablesV2(spec.Variables),
jsonSize: getJSONSize(spec),
}
}
@@ -340,6 +354,7 @@ func collectStatsV2beta1(spec dashv2beta1.DashboardSpec) dashboardStats {
annotationCount: countAnnotationsV2beta1(spec.Annotations),
linkCount: countLinksV2beta1(spec.Links),
variableCount: countVariablesV2beta1(spec.Variables),
jsonSize: getJSONSize(spec),
}
}
@@ -400,6 +415,20 @@ func detectConversionDataLoss(sourceStats, targetStats dashboardStats, sourceFun
))
}
// JSON size: detect significant decrease (>20%) which may indicate content loss
// This catches cases like text panel content being replaced with defaults
if sourceStats.jsonSize > 0 && targetStats.jsonSize > 0 {
decreasePercent := float64(sourceStats.jsonSize-targetStats.jsonSize) / float64(sourceStats.jsonSize) * 100
if decreasePercent > 20 {
errors = append(errors, fmt.Sprintf(
"JSON size decreased significantly: source=%d bytes, target=%d bytes (%.1f%% decrease, possible content loss)",
sourceStats.jsonSize,
targetStats.jsonSize,
decreasePercent,
))
}
}
if len(errors) > 0 {
errorMsg := fmt.Sprintf("%v", errors)
// Note: sourceAPIVersion and targetAPIVersion are passed from checkConversionDataLoss
@@ -189,6 +189,8 @@ func withConversionMetrics(sourceVersionAPI, targetVersionAPI string, conversion
"annotationsLost", math.Max(0, float64(sourceStats.annotationCount-targetStats.annotationCount)),
"linksLost", math.Max(0, float64(sourceStats.linkCount-targetStats.linkCount)),
"variablesLost", math.Max(0, float64(sourceStats.variableCount-targetStats.variableCount)),
"sourceJSONSize", sourceStats.jsonSize,
"targetJSONSize", targetStats.jsonSize,
)
}
@@ -222,11 +224,19 @@ func withConversionMetrics(sourceVersionAPI, targetVersionAPI string, conversion
).Inc()
// Log success (debug level to avoid spam)
// Collect stats for logging
sourceStats := collectDashboardStats(a)
targetStats := collectDashboardStats(b)
// Build base log fields for success
successLogFields := []interface{}{
"sourceVersionAPI", sourceVersionAPI,
"targetVersionAPI", targetVersionAPI,
"dashboardUID", dashboardUID,
"panelCount", targetStats.panelCount,
"queryCount", targetStats.queryCount,
"sourceJsonSize", sourceStats.jsonSize,
"targetJsonSize", targetStats.jsonSize,
}
// Add schema version fields only if we have them (v0/v1 dashboards)
+4
View File
@@ -699,6 +699,10 @@ export interface FeatureToggles {
*/
playlistsReconciler?: boolean;
/**
* Enable passwordless login via magic link authentication
*/
passwordlessMagicLinkAuthentication?: boolean;
/**
* Display Related Logs in Grafana Metrics Drilldown
*/
exploreMetricsRelatedLogs?: boolean;
+2 -1
View File
@@ -226,7 +226,8 @@ func (hs *HTTPServer) registerRoutes() {
r.Post("/api/user/email/start-verify", reqSignedInNoAnonymous, routing.Wrap(hs.StartEmailVerificaton))
}
if hs.Cfg.PasswordlessMagicLinkAuth.Enabled {
//nolint:staticcheck // not yet migrated to OpenFeature
if hs.Cfg.PasswordlessMagicLinkAuth.Enabled && hs.Features.IsEnabledGlobally(featuremgmt.FlagPasswordlessMagicLinkAuthentication) {
r.Post("/api/login/passwordless/start", requestmeta.SetOwner(requestmeta.TeamAuth), quota(string(auth.QuotaTargetSrv)), hs.StartPasswordless)
r.Post("/api/login/passwordless/authenticate", requestmeta.SetOwner(requestmeta.TeamAuth), quota(string(auth.QuotaTargetSrv)), routing.Wrap(hs.LoginPasswordless))
}
+2 -1
View File
@@ -410,7 +410,8 @@ func (hs *HTTPServer) getFrontendSettings(c *contextmodel.ReqContext) (*dtos.Fro
DisableSignoutMenu: hs.Cfg.DisableSignoutMenu,
}
if hs.Cfg.PasswordlessMagicLinkAuth.Enabled {
//nolint:staticcheck // not yet migrated to OpenFeature
if hs.Cfg.PasswordlessMagicLinkAuth.Enabled && hs.Features.IsEnabled(c.Req.Context(), featuremgmt.FlagPasswordlessMagicLinkAuthentication) {
hasEnabledProviders := hs.samlEnabled() || hs.authnService.IsClientEnabled(authn.ClientLDAP)
if !hasEnabledProviders {
+4 -1
View File
@@ -1,6 +1,8 @@
package authnimpl
import (
"context"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/infra/remotecache"
"github.com/grafana/grafana/pkg/infra/tracing"
@@ -81,7 +83,8 @@ func ProvideRegistration(
}
}
if cfg.PasswordlessMagicLinkAuth.Enabled {
//nolint:staticcheck // not yet migrated to OpenFeature
if cfg.PasswordlessMagicLinkAuth.Enabled && features.IsEnabled(context.Background(), featuremgmt.FlagPasswordlessMagicLinkAuthentication) {
hasEnabledProviders := authnSvc.IsClientEnabled(authn.ClientSAML) || authnSvc.IsClientEnabled(authn.ClientLDAP)
if !hasEnabledProviders {
oauthInfos := socialService.GetOAuthInfoProviders()
+8 -1
View File
@@ -1155,7 +1155,14 @@ var (
Owner: grafanaAppPlatformSquad,
RequiresRestart: true,
},
{
{
Name: "passwordlessMagicLinkAuthentication",
Description: "Enable passwordless login via magic link authentication",
Stage: FeatureStageExperimental,
Owner: identityAccessTeam,
HideFromDocs: true,
},
{
Name: "exploreMetricsRelatedLogs",
Description: "Display Related Logs in Grafana Metrics Drilldown",
Stage: FeatureStageExperimental,
+1
View File
@@ -160,6 +160,7 @@ timeRangePan,experimental,@grafana/dataviz-squad,false,false,true
newTimeRangeZoomShortcuts,experimental,@grafana/dataviz-squad,false,false,true
azureMonitorDisableLogLimit,GA,@grafana/partner-datasources,false,false,false
playlistsReconciler,experimental,@grafana/grafana-app-platform-squad,false,true,false
passwordlessMagicLinkAuthentication,experimental,@grafana/identity-access-team,false,false,false
exploreMetricsRelatedLogs,experimental,@grafana/observability-metrics,false,false,true
prometheusSpecialCharsInLabelValues,experimental,@grafana/oss-big-tent,false,false,true
enableExtensionsAdminPage,experimental,@grafana/plugins-platform-backend,false,true,false
1 Name Stage Owner requiresDevMode RequiresRestart FrontendOnly
160 newTimeRangeZoomShortcuts experimental @grafana/dataviz-squad false false true
161 azureMonitorDisableLogLimit GA @grafana/partner-datasources false false false
162 playlistsReconciler experimental @grafana/grafana-app-platform-squad false true false
163 passwordlessMagicLinkAuthentication experimental @grafana/identity-access-team false false false
164 exploreMetricsRelatedLogs experimental @grafana/observability-metrics false false true
165 prometheusSpecialCharsInLabelValues experimental @grafana/oss-big-tent false false true
166 enableExtensionsAdminPage experimental @grafana/plugins-platform-backend false true false
+4
View File
@@ -483,6 +483,10 @@ const (
// Enables experimental reconciler for playlists
FlagPlaylistsReconciler = "playlistsReconciler"
// FlagPasswordlessMagicLinkAuthentication
// Enable passwordless login via magic link authentication
FlagPasswordlessMagicLinkAuthentication = "passwordlessMagicLinkAuthentication"
// FlagEnableExtensionsAdminPage
// Enables the extension admin page regardless of development mode
FlagEnableExtensionsAdminPage = "enableExtensionsAdminPage"
+1 -2
View File
@@ -2661,8 +2661,7 @@
"metadata": {
"name": "passwordlessMagicLinkAuthentication",
"resourceVersion": "1764664939750",
"creationTimestamp": "2024-11-14T13:50:55Z",
"deletionTimestamp": "2026-01-08T15:33:29Z"
"creationTimestamp": "2024-11-14T13:50:55Z"
},
"spec": {
"description": "Enable passwordless login via magic link authentication",